Repository: maplibre/maplibre-tile-spec Branch: main Commit: ad5e4b051edc Files: 1794 Total size: 71.3 MB Directory structure: gitextract_0pgzvh0v/ ├── .clang-format ├── .cursor/ │ └── rules/ │ └── karpathy-guidelines.mdc ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── actions/ │ │ ├── mlt-setup-java/ │ │ │ └── action.yml │ │ ├── mlt-setup-node/ │ │ │ └── action.yml │ │ └── mlt-setup-rust/ │ │ └── action.yml │ ├── dependabot.yml │ └── workflows/ │ ├── autofix.yml │ ├── ci.yml │ ├── cpp.yml │ ├── dependabot.yml │ ├── gh-pages.yml │ ├── hotpath-comment.yml │ ├── hotpath-profile.yml │ ├── java-release.yml │ ├── java.yml │ ├── python-release.yml │ ├── rust.yml │ ├── ts-bump-version.yml │ ├── ts-release.yml │ └── ts.yml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── CODE_OF_CONDUCT.md ├── LICENSE-APACHE ├── LICENSE-CC0 ├── LICENSE-MIT ├── MODULE.bazel ├── README.md ├── SECURITY_POLICY.txt ├── biome.json ├── cpp/ │ ├── .clang-tidy │ ├── .gitignore │ ├── .nvmrc │ ├── BUILD.bazel │ ├── CMakeLists.txt │ ├── CTestCustom.cmake │ ├── README.md │ ├── bazel/ │ │ └── check/ │ │ ├── BUILD.bazel │ │ ├── avx.c │ │ ├── avx2.c │ │ └── sse42.c │ ├── cmake/ │ │ └── AddCXXCompilerFlag.cmake │ ├── include/ │ │ └── mlt/ │ │ ├── common.hpp │ │ ├── coordinate.hpp │ │ ├── decoder.hpp │ │ ├── feature.hpp │ │ ├── geometry.hpp │ │ ├── geometry_vector.hpp │ │ ├── json.hpp │ │ ├── layer.hpp │ │ ├── metadata/ │ │ │ ├── stream.hpp │ │ │ ├── tileset.hpp │ │ │ └── type_map.hpp │ │ ├── polyfill.hpp │ │ ├── projection.hpp │ │ ├── properties.hpp │ │ ├── tile.hpp │ │ └── util/ │ │ ├── buffer_stream.hpp │ │ ├── noncopyable.hpp │ │ ├── packed_bitset.hpp │ │ ├── stl.hpp │ │ └── varint.hpp │ ├── mod.just │ ├── package.json │ ├── src/ │ │ └── mlt/ │ │ ├── decode/ │ │ │ ├── geometry.hpp │ │ │ ├── int.cpp │ │ │ ├── int.hpp │ │ │ ├── int_template.hpp │ │ │ ├── property.hpp │ │ │ └── string.hpp │ │ ├── decoder.cpp │ │ ├── feature.cpp │ │ ├── geometry_vector.cpp │ │ ├── layer.cpp │ │ ├── metadata/ │ │ │ ├── stream.cpp │ │ │ └── tileset.cpp │ │ ├── properties.cpp │ │ └── util/ │ │ ├── json_diff.hpp │ │ ├── morton_curve.hpp │ │ ├── raw.hpp │ │ ├── rle.cpp │ │ ├── rle.hpp │ │ ├── space_filling_curve.hpp │ │ ├── vectorized.hpp │ │ └── zigzag.hpp │ ├── test/ │ │ ├── CMakeLists.txt │ │ ├── test_decode.cpp │ │ ├── test_fastpfor.cpp │ │ ├── test_fsst.cpp │ │ ├── test_packed_bitset.cpp │ │ ├── test_util.cpp │ │ └── test_varint.cpp │ ├── tool/ │ │ ├── CMakeLists.txt │ │ ├── mlt-json.cpp │ │ ├── synthetic-geojson.cpp │ │ ├── synthetic-geojson.hpp │ │ └── synthetic.test.ts │ └── vendor/ │ └── fastpfor.cmake ├── docs/ │ ├── assets/ │ │ ├── extra.css │ │ └── spec/ │ │ ├── mlt_tileset_metadata.json │ │ └── place_feature.json │ ├── encodings.md │ ├── implementation-status.md │ ├── index.md │ ├── snippets/ │ │ └── live-spec-note │ └── specification.md ├── java/ │ ├── .gitignore │ ├── CONTRIBUTING.md │ ├── README-Decode.md │ ├── README-Encode.md │ ├── README.MD │ ├── encoding-server/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── config.json │ │ ├── config.mjs │ │ ├── convert.mjs │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ └── server.mjs │ ├── gradle/ │ │ ├── libs.versions.toml │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── lombok.config │ ├── mlt-cli/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── kotlin/ │ │ │ │ └── org/ │ │ │ │ └── maplibre/ │ │ │ │ └── mlt/ │ │ │ │ └── cli/ │ │ │ │ ├── Conversion.kt │ │ │ │ ├── Decode.kt │ │ │ │ ├── Encode.kt │ │ │ │ ├── EncodeCommandLine.kt │ │ │ │ ├── EncodeConfig.kt │ │ │ │ ├── Environment.kt │ │ │ │ ├── Json.kt │ │ │ │ ├── Logging.kt │ │ │ │ ├── MBTiles.kt │ │ │ │ ├── OfflineDB.kt │ │ │ │ ├── PMTiles.kt │ │ │ │ ├── ReadablePmtiles.kt │ │ │ │ ├── SerialTaskRunner.kt │ │ │ │ ├── Server.kt │ │ │ │ ├── TaskRunner.kt │ │ │ │ ├── ThreadPoolTaskRunner.kt │ │ │ │ └── Timer.kt │ │ │ └── resources/ │ │ │ └── log4j2.json │ │ └── test/ │ │ └── kotlin/ │ │ └── org/ │ │ └── maplibre/ │ │ └── mlt/ │ │ └── cli/ │ │ ├── EncodeCommandLineTest.kt │ │ ├── EnvironmentResolverTest.kt │ │ ├── LoggingTest.kt │ │ ├── ReadablePmtilesMapDirectoryTest.kt │ │ ├── TaskRunnerTest.kt │ │ ├── TestUtil.kt │ │ └── TileCoordRangeTest.kt │ ├── mlt-core/ │ │ ├── build.gradle │ │ └── src/ │ │ ├── jmh/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── maplibre/ │ │ │ └── mlt/ │ │ │ ├── BenchmarkUtils.java │ │ │ ├── OmtDecoderBenchmark.java │ │ │ ├── converter/ │ │ │ │ └── encodings/ │ │ │ │ └── fsst/ │ │ │ │ └── FsstBenchmark.java │ │ │ └── data/ │ │ │ ├── rle_PartOffsets.csv │ │ │ ├── rle_class_ratio17.csv │ │ │ └── rle_id_ratio22_45k.csv │ │ ├── main/ │ │ │ └── java/ │ │ │ ├── org/ │ │ │ │ └── maplibre/ │ │ │ │ └── mlt/ │ │ │ │ ├── compare/ │ │ │ │ │ └── CompareHelper.java │ │ │ │ ├── converter/ │ │ │ │ │ ├── CollectionUtils.java │ │ │ │ │ ├── ColumnMapping.java │ │ │ │ │ ├── ColumnMappingConfig.java │ │ │ │ │ ├── ConversionConfig.java │ │ │ │ │ ├── FeatureTableOptimizations.java │ │ │ │ │ ├── MltConverter.java │ │ │ │ │ ├── MortonSettings.java │ │ │ │ │ ├── encodings/ │ │ │ │ │ │ ├── BooleanEncoder.java │ │ │ │ │ │ ├── ByteRleEncoder.java │ │ │ │ │ │ ├── DoubleEncoder.java │ │ │ │ │ │ ├── EncodingUtils.java │ │ │ │ │ │ ├── FloatEncoder.java │ │ │ │ │ │ ├── GeometryEncoder.java │ │ │ │ │ │ ├── IntegerEncoder.java │ │ │ │ │ │ ├── LinearRegression.java │ │ │ │ │ │ ├── MltTypeMap.java │ │ │ │ │ │ ├── PropertyEncoder.java │ │ │ │ │ │ ├── StringEncoder.java │ │ │ │ │ │ └── fsst/ │ │ │ │ │ │ ├── Fsst.java │ │ │ │ │ │ ├── FsstDebug.java │ │ │ │ │ │ ├── FsstEncoder.java │ │ │ │ │ │ ├── FsstJava.java │ │ │ │ │ │ ├── FsstJni.java │ │ │ │ │ │ ├── Symbol.java │ │ │ │ │ │ ├── SymbolTable.java │ │ │ │ │ │ └── SymbolTableBuilder.java │ │ │ │ │ ├── geometry/ │ │ │ │ │ │ ├── GeometryType.java │ │ │ │ │ │ ├── GeometryUtils.java │ │ │ │ │ │ ├── Hilbert.java │ │ │ │ │ │ ├── HilbertCurve.java │ │ │ │ │ │ ├── SpaceFillingCurve.java │ │ │ │ │ │ ├── Vertex.java │ │ │ │ │ │ └── ZOrderCurve.java │ │ │ │ │ ├── mvt/ │ │ │ │ │ │ └── MvtUtils.java │ │ │ │ │ └── tessellation/ │ │ │ │ │ ├── TessellatedPolygon.java │ │ │ │ │ └── TessellationUtils.java │ │ │ │ ├── data/ │ │ │ │ │ ├── Feature.java │ │ │ │ │ ├── FeatureInterface.java │ │ │ │ │ ├── IndexedProperty.java │ │ │ │ │ ├── Layer.java │ │ │ │ │ ├── LayerSource.java │ │ │ │ │ ├── MLTFeature.java │ │ │ │ │ ├── MVTFeature.java │ │ │ │ │ ├── MapLibreTile.java │ │ │ │ │ ├── MapboxVectorTile.java │ │ │ │ │ ├── Property.java │ │ │ │ │ └── unsigned/ │ │ │ │ │ ├── U32.java │ │ │ │ │ ├── U64.java │ │ │ │ │ ├── U8.java │ │ │ │ │ └── Unsigned.java │ │ │ │ ├── decoder/ │ │ │ │ │ ├── ByteRleDecoder.java │ │ │ │ │ ├── DecodingUtils.java │ │ │ │ │ ├── DoubleDecoder.java │ │ │ │ │ ├── FloatDecoder.java │ │ │ │ │ ├── GeometryDecoder.java │ │ │ │ │ ├── IntegerDecoder.java │ │ │ │ │ ├── MltDecoder.java │ │ │ │ │ ├── PropertyDecoder.java │ │ │ │ │ ├── StringDecoder.java │ │ │ │ │ └── vectorized/ │ │ │ │ │ └── VectorizedDecodingUtils.java │ │ │ │ ├── json/ │ │ │ │ │ └── Json.java │ │ │ │ ├── metadata/ │ │ │ │ │ ├── stream/ │ │ │ │ │ │ ├── DictionaryType.java │ │ │ │ │ │ ├── LengthType.java │ │ │ │ │ │ ├── LogicalLevelTechnique.java │ │ │ │ │ │ ├── LogicalStreamType.java │ │ │ │ │ │ ├── MortonEncodedStreamMetadata.java │ │ │ │ │ │ ├── OffsetType.java │ │ │ │ │ │ ├── PhysicalLevelTechnique.java │ │ │ │ │ │ ├── PhysicalStreamType.java │ │ │ │ │ │ ├── RleEncodedStreamMetadata.java │ │ │ │ │ │ ├── StreamMetadata.java │ │ │ │ │ │ └── StreamMetadataDecoder.java │ │ │ │ │ └── tileset/ │ │ │ │ │ └── MltMetadata.java │ │ │ │ └── util/ │ │ │ │ ├── ByteArrayUtil.java │ │ │ │ ├── ExceptionUtil.java │ │ │ │ ├── OptionalUtil.java │ │ │ │ └── StreamUtil.java │ │ │ └── springmeyer/ │ │ │ ├── Pbf.java │ │ │ ├── Point.java │ │ │ ├── VectorTile.java │ │ │ ├── VectorTileFeature.java │ │ │ └── VectorTileLayer.java │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── maplibre/ │ │ └── mlt/ │ │ ├── MltGenerator.java │ │ ├── TestSettings.java │ │ ├── TestUtils.java │ │ ├── benchmarks/ │ │ │ ├── CompressionBenchmarksTest.java │ │ │ └── MltDecoderBenchmarkTest.java │ │ ├── compare/ │ │ │ └── CompareHelperTest.java │ │ ├── converter/ │ │ │ ├── ConversionConfigTest.java │ │ │ ├── MltConverterTest.java │ │ │ ├── encodings/ │ │ │ │ ├── EncodingUtilsTest.java │ │ │ │ ├── LinearRegressionTest.java │ │ │ │ ├── MltTypeMapTest.java │ │ │ │ ├── VarintTest.java │ │ │ │ └── fsst/ │ │ │ │ ├── FsstTest.java │ │ │ │ └── SymbolTest.java │ │ │ ├── geometry/ │ │ │ │ ├── HilbertCurveTest.java │ │ │ │ ├── SpaceFillingCurveTest.java │ │ │ │ └── ZOrderCurveTest.java │ │ │ └── tessellation/ │ │ │ └── TessellationUtilsTest.java │ │ ├── data/ │ │ │ └── unsigned/ │ │ │ └── UnsignedTest.java │ │ ├── decoder/ │ │ │ ├── ByteRleTest.java │ │ │ ├── DecodingUtilsTest.java │ │ │ ├── DoubleDecoderTest.java │ │ │ ├── IntegerDecoderTest.java │ │ │ ├── MltDecoderTest.java │ │ │ ├── MltDecoderTest2.java │ │ │ └── StringDecoderTest.java │ │ ├── json/ │ │ │ └── JsonTest.java │ │ ├── metadata/ │ │ │ └── tileset/ │ │ │ └── MltMetadataColumnTest.java │ │ ├── synthetics/ │ │ │ └── SyntheticsTest.java │ │ └── util/ │ │ ├── OptionalUtilTest.java │ │ └── StreamUtilTest.java │ ├── mlt-tools/ │ │ ├── build.gradle │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── org/ │ │ └── maplibre/ │ │ └── mlt/ │ │ └── tools/ │ │ ├── SyntheticMltGenerator.java │ │ └── SyntheticMltUtil.java │ ├── mod.just │ ├── resources/ │ │ ├── CMakeLists.txt │ │ ├── FsstWrapper.cpp │ │ ├── FsstWrapper.h │ │ ├── compile │ │ └── compile-windows.bat │ ├── settings.gradle │ └── tessellation/ │ ├── index.mjs │ └── package.json ├── justfile ├── mkdocs.yml ├── qgis/ │ ├── .gitignore │ ├── README.md │ └── mlt_plugin/ │ ├── __init__.py │ ├── loader.py │ ├── metadata.txt │ ├── plugin.py │ └── tile_coords.py ├── release-plz.toml ├── rust/ │ ├── .gitignore │ ├── CONTRIBUTING.md │ ├── Cargo.toml │ ├── README.md │ ├── bench_param.sh │ ├── clippy.toml │ ├── mlt/ │ │ ├── CHANGELOG.md │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── convert/ │ │ │ ├── files.rs │ │ │ ├── mod.rs │ │ │ └── tileset.rs │ │ ├── dump.rs │ │ ├── ls.rs │ │ ├── main.rs │ │ └── ui/ │ │ ├── mbt.rs │ │ ├── mod.rs │ │ ├── rendering/ │ │ │ ├── files.rs │ │ │ ├── help.rs │ │ │ ├── layers.rs │ │ │ ├── map.rs │ │ │ └── mod.rs │ │ └── state.rs │ ├── mlt-core/ │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── benches/ │ │ │ ├── bench_utils.rs │ │ │ ├── decoding_e2e.rs │ │ │ ├── decoding_strings.rs │ │ │ ├── decoding_utils.rs │ │ │ ├── encoding_e2e.rs │ │ │ └── encoding_from_mvt.rs │ │ ├── fuzz/ │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── fuzz_targets/ │ │ │ │ ├── decoded_layer.rs │ │ │ │ └── layer.rs │ │ │ ├── src/ │ │ │ │ ├── decoded_layer.rs │ │ │ │ ├── layer.rs │ │ │ │ └── lib.rs │ │ │ └── tests/ │ │ │ └── reproduce.rs │ │ ├── src/ │ │ │ ├── codecs/ │ │ │ │ ├── bytes.rs │ │ │ │ ├── fastpfor.rs │ │ │ │ ├── fsst.rs │ │ │ │ ├── hilbert.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── morton.rs │ │ │ │ ├── rle.rs │ │ │ │ ├── varint.rs │ │ │ │ └── zigzag.rs │ │ │ ├── convert/ │ │ │ │ ├── geojson.rs │ │ │ │ ├── mod.rs │ │ │ │ └── mvt.rs │ │ │ ├── decoder/ │ │ │ │ ├── analyze.rs │ │ │ │ ├── column.rs │ │ │ │ ├── fuzzing.rs │ │ │ │ ├── geometry/ │ │ │ │ │ ├── decode.rs │ │ │ │ │ ├── geotype.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── model.rs │ │ │ │ ├── id/ │ │ │ │ │ ├── decode.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── model.rs │ │ │ │ ├── iterators.rs │ │ │ │ ├── layer.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── model.rs │ │ │ │ ├── property/ │ │ │ │ │ ├── decode.rs │ │ │ │ │ ├── geojson.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── model.rs │ │ │ │ │ └── strings.rs │ │ │ │ ├── root.rs │ │ │ │ ├── stream/ │ │ │ │ │ ├── analyze.rs │ │ │ │ │ ├── decode.rs │ │ │ │ │ ├── logical.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── model.rs │ │ │ │ │ ├── parse.rs │ │ │ │ │ └── physical.rs │ │ │ │ └── tile.rs │ │ │ ├── encoder/ │ │ │ │ ├── analyze.rs │ │ │ │ ├── fuzzing.rs │ │ │ │ ├── geometry/ │ │ │ │ │ ├── encode.rs │ │ │ │ │ ├── geotype.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── model.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── id/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── staged_id.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── model.rs │ │ │ │ ├── optimizer.rs │ │ │ │ ├── property/ │ │ │ │ │ ├── encode.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── model.rs │ │ │ │ │ ├── shared_dict.rs │ │ │ │ │ ├── strings.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── sort.rs │ │ │ │ ├── stream/ │ │ │ │ │ ├── codecs.rs │ │ │ │ │ ├── encode_stream.rs │ │ │ │ │ ├── encoder.rs │ │ │ │ │ ├── logical.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── model.rs │ │ │ │ │ ├── optimizer.rs │ │ │ │ │ ├── physical.rs │ │ │ │ │ ├── tests.rs │ │ │ │ │ └── write.rs │ │ │ │ ├── tests.rs │ │ │ │ ├── tile.rs │ │ │ │ ├── unknown.rs │ │ │ │ └── writer.rs │ │ │ ├── errors.rs │ │ │ ├── lib.rs │ │ │ └── utils/ │ │ │ ├── analyze.rs │ │ │ ├── extensions.rs │ │ │ ├── formatter.rs │ │ │ ├── lazy_state.rs │ │ │ ├── mod.rs │ │ │ ├── parse.rs │ │ │ ├── presence.rs │ │ │ ├── serialize.rs │ │ │ └── test_helpers.rs │ │ └── tests/ │ │ ├── geojson.rs │ │ ├── snapshots.rs │ │ └── unknown_layer.rs │ ├── mlt-py/ │ │ ├── CHANGELOG.md │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── maplibre_tiles.pyi │ │ ├── pyproject.toml │ │ └── src/ │ │ ├── bins/ │ │ │ └── stub_gen.rs │ │ ├── feature.rs │ │ ├── lib.rs │ │ └── tile_transform.rs │ ├── mlt-synthetics/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── layer.rs │ │ ├── main.rs │ │ └── writer.rs │ ├── mlt-wasm/ │ │ ├── .gitignore │ │ ├── .nvmrc │ │ ├── CHANGELOG.md │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── js/ │ │ │ ├── decoder.bench.ts │ │ │ ├── index.ts │ │ │ ├── synthetic.spec.ts │ │ │ └── vectorTile.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── geometry.rs │ │ │ ├── layer.rs │ │ │ ├── lib.rs │ │ │ ├── properties.rs │ │ │ └── tile.rs │ │ ├── tsconfig.json │ │ └── vitest.config.ts │ ├── mod.just │ └── tomlfmt.toml ├── spec/ │ └── schema/ │ └── mlt_tileset_metadata.proto ├── test/ │ ├── .gitignore │ ├── .java-version │ ├── convert_tiles │ ├── expected/ │ │ └── tag0x01/ │ │ ├── amazon/ │ │ │ ├── 10_518_352.mlt │ │ │ ├── 11_1037_704.mlt │ │ │ ├── 5_16_11.mlt │ │ │ ├── 5_5_11.mlt │ │ │ ├── 5_6_21.mlt │ │ │ ├── 5_8_12.mlt │ │ │ ├── 6_33_21.mlt │ │ │ ├── 6_33_22.mlt │ │ │ ├── 7_68_44.mlt │ │ │ ├── 8_136_89.mlt │ │ │ └── 9_259_176.mlt │ │ ├── amazon_here/ │ │ │ ├── 4_8_5.mlt │ │ │ ├── 4_9_4.mlt │ │ │ ├── 5_16_10.mlt │ │ │ ├── 6_33_22.mlt │ │ │ └── 8_132_85.mlt │ │ ├── bing/ │ │ │ ├── 4-12-6.mlt │ │ │ ├── 4-13-6.mlt │ │ │ ├── 4-8-5.mlt │ │ │ ├── 4-9-5.mlt │ │ │ ├── 5-15-10.mlt │ │ │ ├── 5-16-11.mlt │ │ │ ├── 5-16-9.mlt │ │ │ ├── 5-17-10.mlt │ │ │ ├── 5-17-11.mlt │ │ │ ├── 6-32-21.mlt │ │ │ ├── 6-32-22.mlt │ │ │ ├── 6-32-23.mlt │ │ │ ├── 6-33-22.mlt │ │ │ ├── 7-65-42.mlt │ │ │ ├── 7-66-42.mlt │ │ │ ├── 7-66-43.mlt │ │ │ └── 7-66-44.mlt │ │ ├── omt/ │ │ │ ├── 0_0_0.mlt │ │ │ ├── 10_530_682.mlt │ │ │ ├── 10_530_683.mlt │ │ │ ├── 10_530_684.mlt │ │ │ ├── 10_531_682.mlt │ │ │ ├── 10_531_683.mlt │ │ │ ├── 10_531_684.mlt │ │ │ ├── 10_532_682.mlt │ │ │ ├── 10_532_683.mlt │ │ │ ├── 10_532_684.mlt │ │ │ ├── 10_533_682.mlt │ │ │ ├── 10_533_683.mlt │ │ │ ├── 10_533_684.mlt │ │ │ ├── 11_1062_1366.mlt │ │ │ ├── 11_1062_1367.mlt │ │ │ ├── 11_1062_1368.mlt │ │ │ ├── 11_1063_1366.mlt │ │ │ ├── 11_1063_1367.mlt │ │ │ ├── 11_1063_1368.mlt │ │ │ ├── 11_1064_1366.mlt │ │ │ ├── 11_1064_1367.mlt │ │ │ ├── 11_1064_1368.mlt │ │ │ ├── 11_1065_1366.mlt │ │ │ ├── 11_1065_1367.mlt │ │ │ ├── 11_1065_1368.mlt │ │ │ ├── 12_2130_2733.mlt │ │ │ ├── 12_2130_2734.mlt │ │ │ ├── 12_2131_2733.mlt │ │ │ ├── 12_2131_2734.mlt │ │ │ ├── 12_2132_2733.mlt │ │ │ ├── 12_2132_2734.mlt │ │ │ ├── 12_2133_2733.mlt │ │ │ ├── 12_2133_2734.mlt │ │ │ ├── 12_2134_2733.mlt │ │ │ ├── 12_2134_2734.mlt │ │ │ ├── 13_4264_5467.mlt │ │ │ ├── 13_4264_5468.mlt │ │ │ ├── 13_4265_5467.mlt │ │ │ ├── 13_4265_5468.mlt │ │ │ ├── 13_4266_5467.mlt │ │ │ ├── 13_4266_5468.mlt │ │ │ ├── 13_4267_5467.mlt │ │ │ ├── 13_4267_5468.mlt │ │ │ ├── 14_8296_10748.mlt │ │ │ ├── 14_8296_10749.mlt │ │ │ ├── 14_8297_10748.mlt │ │ │ ├── 14_8297_10749.mlt │ │ │ ├── 14_8298_10748.mlt │ │ │ ├── 14_8298_10749.mlt │ │ │ ├── 14_8299_10748.mlt │ │ │ ├── 14_8299_10749.mlt │ │ │ ├── 14_8300_10748.mlt │ │ │ ├── 14_8300_10749.mlt │ │ │ ├── 1_1_1.mlt │ │ │ ├── 2_2_2.mlt │ │ │ ├── 3_4_5.mlt │ │ │ ├── 4_3_9.mlt │ │ │ ├── 4_8_10.mlt │ │ │ ├── 5_16_11.mlt │ │ │ ├── 5_16_20.mlt │ │ │ ├── 5_16_21.mlt │ │ │ ├── 5_17_20.mlt │ │ │ ├── 5_17_21.mlt │ │ │ ├── 6_32_41.mlt │ │ │ ├── 6_32_42.mlt │ │ │ ├── 6_33_41.mlt │ │ │ ├── 6_33_42.mlt │ │ │ ├── 6_34_41.mlt │ │ │ ├── 6_34_42.mlt │ │ │ ├── 7_66_83.mlt │ │ │ ├── 7_66_84.mlt │ │ │ ├── 7_66_85.mlt │ │ │ ├── 7_67_83.mlt │ │ │ ├── 7_67_84.mlt │ │ │ ├── 7_67_85.mlt │ │ │ ├── 7_68_83.mlt │ │ │ ├── 7_68_84.mlt │ │ │ ├── 7_68_85.mlt │ │ │ ├── 8_132_170.mlt │ │ │ ├── 8_132_171.mlt │ │ │ ├── 8_133_170.mlt │ │ │ ├── 8_133_171.mlt │ │ │ ├── 8_134_170.mlt │ │ │ ├── 8_134_171.mlt │ │ │ ├── 8_135_170.mlt │ │ │ ├── 8_135_171.mlt │ │ │ ├── 9_264_340.mlt │ │ │ ├── 9_264_341.mlt │ │ │ ├── 9_264_342.mlt │ │ │ ├── 9_265_340.mlt │ │ │ ├── 9_265_341.mlt │ │ │ ├── 9_265_342.mlt │ │ │ ├── 9_266_340.mlt │ │ │ ├── 9_266_341.mlt │ │ │ └── 9_266_342.mlt │ │ └── simple/ │ │ ├── LICENSE │ │ ├── line-boolean.mlt │ │ ├── line-boolean.mlt.geojson │ │ ├── multiline-boolean.mlt │ │ ├── multiline-boolean.mlt.geojson │ │ ├── multipoint-boolean.mlt │ │ ├── multipoint-boolean.mlt.geojson │ │ ├── multipolygon-boolean.mlt │ │ ├── multipolygon-boolean.mlt.geojson │ │ ├── point-boolean.mlt │ │ ├── point-boolean.mlt.geojson │ │ ├── polygon-boolean.mlt │ │ └── polygon-boolean.mlt.geojson │ ├── fixtures/ │ │ ├── amazon/ │ │ │ ├── 10_518_352.mvt │ │ │ ├── 11_1037_704.mvt │ │ │ ├── 5_16_11.mvt │ │ │ ├── 5_5_11.mvt │ │ │ ├── 5_6_21.mvt │ │ │ ├── 5_8_12.mvt │ │ │ ├── 6_33_21.mvt │ │ │ ├── 6_33_22.mvt │ │ │ ├── 7_68_44.mvt │ │ │ ├── 8_136_89.mvt │ │ │ └── 9_259_176.mvt │ │ ├── amazon_here/ │ │ │ ├── 4_8_5.mvt │ │ │ ├── 4_9_4.mvt │ │ │ ├── 5_16_10.mvt │ │ │ ├── 6_33_22.mvt │ │ │ └── 8_132_85.mvt │ │ ├── bing/ │ │ │ ├── 4-12-6.mvt │ │ │ ├── 4-13-6.mvt │ │ │ ├── 4-8-5.mvt │ │ │ ├── 4-9-5.mvt │ │ │ ├── 5-15-10.mvt │ │ │ ├── 5-16-11.mvt │ │ │ ├── 5-16-9.mvt │ │ │ ├── 5-17-10.mvt │ │ │ ├── 5-17-11.mvt │ │ │ ├── 6-32-21.mvt │ │ │ ├── 6-32-22.mvt │ │ │ ├── 6-32-23.mvt │ │ │ ├── 6-33-22.mvt │ │ │ ├── 7-65-42.mvt │ │ │ ├── 7-66-42.mvt │ │ │ ├── 7-66-43.mvt │ │ │ └── 7-66-44.mvt │ │ ├── fastpfor/ │ │ │ └── README.md │ │ ├── omt/ │ │ │ ├── 0_0_0.mvt │ │ │ ├── 10_530_682.mvt │ │ │ ├── 10_530_683.mvt │ │ │ ├── 10_530_684.mvt │ │ │ ├── 10_531_682.mvt │ │ │ ├── 10_531_683.mvt │ │ │ ├── 10_531_684.mvt │ │ │ ├── 10_532_682.mvt │ │ │ ├── 10_532_683.mvt │ │ │ ├── 10_532_684.mvt │ │ │ ├── 10_533_682.mvt │ │ │ ├── 10_533_683.mvt │ │ │ ├── 10_533_684.mvt │ │ │ ├── 11_1062_1366.mvt │ │ │ ├── 11_1062_1367.mvt │ │ │ ├── 11_1062_1368.mvt │ │ │ ├── 11_1063_1366.mvt │ │ │ ├── 11_1063_1367.mvt │ │ │ ├── 11_1063_1368.mvt │ │ │ ├── 11_1064_1366.mvt │ │ │ ├── 11_1064_1367.mvt │ │ │ ├── 11_1064_1368.mvt │ │ │ ├── 11_1065_1366.mvt │ │ │ ├── 11_1065_1367.mvt │ │ │ ├── 11_1065_1368.mvt │ │ │ ├── 12_2130_2733.mvt │ │ │ ├── 12_2130_2734.mvt │ │ │ ├── 12_2131_2733.mvt │ │ │ ├── 12_2131_2734.mvt │ │ │ ├── 12_2132_2733.mvt │ │ │ ├── 12_2132_2734.mvt │ │ │ ├── 12_2133_2733.mvt │ │ │ ├── 12_2133_2734.mvt │ │ │ ├── 12_2134_2733.mvt │ │ │ ├── 12_2134_2734.mvt │ │ │ ├── 13_4264_5467.mvt │ │ │ ├── 13_4264_5468.mvt │ │ │ ├── 13_4265_5467.mvt │ │ │ ├── 13_4265_5468.mvt │ │ │ ├── 13_4266_5467.mvt │ │ │ ├── 13_4266_5468.mvt │ │ │ ├── 13_4267_5467.mvt │ │ │ ├── 13_4267_5468.mvt │ │ │ ├── 14_8296_10748.mvt │ │ │ ├── 14_8296_10749.mvt │ │ │ ├── 14_8297_10748.mvt │ │ │ ├── 14_8297_10749.mvt │ │ │ ├── 14_8298_10748.mvt │ │ │ ├── 14_8298_10749.mvt │ │ │ ├── 14_8299_10748.mvt │ │ │ ├── 14_8299_10749.mvt │ │ │ ├── 14_8300_10748.mvt │ │ │ ├── 14_8300_10749.mvt │ │ │ ├── 1_1_1.mvt │ │ │ ├── 2_2_2.mvt │ │ │ ├── 3_4_5.mvt │ │ │ ├── 4_3_9.mvt │ │ │ ├── 4_8_10.mvt │ │ │ ├── 5_16_11.mvt │ │ │ ├── 5_16_20.mvt │ │ │ ├── 5_16_21.mvt │ │ │ ├── 5_17_20.mvt │ │ │ ├── 5_17_21.mvt │ │ │ ├── 6_32_41.mvt │ │ │ ├── 6_32_42.mvt │ │ │ ├── 6_33_41.mvt │ │ │ ├── 6_33_42.mvt │ │ │ ├── 6_34_41.mvt │ │ │ ├── 6_34_42.mvt │ │ │ ├── 7_66_83.mvt │ │ │ ├── 7_66_84.mvt │ │ │ ├── 7_66_85.mvt │ │ │ ├── 7_67_83.mvt │ │ │ ├── 7_67_84.mvt │ │ │ ├── 7_67_85.mvt │ │ │ ├── 7_68_83.mvt │ │ │ ├── 7_68_84.mvt │ │ │ ├── 7_68_85.mvt │ │ │ ├── 8_132_170.mvt │ │ │ ├── 8_132_171.mvt │ │ │ ├── 8_133_170.mvt │ │ │ ├── 8_133_171.mvt │ │ │ ├── 8_134_170.mvt │ │ │ ├── 8_134_171.mvt │ │ │ ├── 8_135_170.mvt │ │ │ ├── 8_135_171.mvt │ │ │ ├── 9_264_340.mvt │ │ │ ├── 9_264_341.mvt │ │ │ ├── 9_264_342.mvt │ │ │ ├── 9_265_340.mvt │ │ │ ├── 9_265_341.mvt │ │ │ ├── 9_265_342.mvt │ │ │ ├── 9_266_340.mvt │ │ │ ├── 9_266_341.mvt │ │ │ └── 9_266_342.mvt │ │ ├── omt-planet-20260112.mvt.max1.pmtiles │ │ ├── omt-planet-20260112.mvt.max1.pmtiles.txt │ │ ├── omt.max1.mbtiles │ │ └── simple/ │ │ ├── LICENSE │ │ ├── line-boolean.mvt │ │ ├── multiline-boolean.mvt │ │ ├── multipoint-boolean.mvt │ │ ├── multipolygon-boolean.mvt │ │ ├── point-boolean.mvt │ │ └── polygon-boolean.mvt │ ├── omt-advanced-mlt.mbtiles │ ├── omt-basic-mlt.mbtiles │ ├── omt-ref.mbtiles │ └── synthetic/ │ ├── 0x01/ │ │ ├── extent_1073741824.json │ │ ├── extent_1073741824.mlt │ │ ├── extent_131072.json │ │ ├── extent_131072.mlt │ │ ├── extent_4096.json │ │ ├── extent_4096.mlt │ │ ├── extent_512.json │ │ ├── extent_512.mlt │ │ ├── extent_buf_1073741824.json │ │ ├── extent_buf_1073741824.mlt │ │ ├── extent_buf_131072.json │ │ ├── extent_buf_131072.mlt │ │ ├── extent_buf_4096.json │ │ ├── extent_buf_4096.mlt │ │ ├── extent_buf_512.json │ │ ├── extent_buf_512.mlt │ │ ├── fpf_align_1.json │ │ ├── fpf_align_1.mlt │ │ ├── fpf_align_2.json │ │ ├── fpf_align_2.mlt │ │ ├── fpf_align_3.json │ │ ├── fpf_align_3.mlt │ │ ├── fpf_align_4.json │ │ ├── fpf_align_4.mlt │ │ ├── fpf_align_5.json │ │ ├── fpf_align_5.mlt │ │ ├── fpf_align_6.json │ │ ├── fpf_align_6.mlt │ │ ├── fpf_align_7.json │ │ ├── fpf_align_7.mlt │ │ ├── fpf_align_8.json │ │ ├── fpf_align_8.mlt │ │ ├── id.json │ │ ├── id.mlt │ │ ├── id64.json │ │ ├── id64.mlt │ │ ├── id_min.json │ │ ├── id_min.mlt │ │ ├── ids.json │ │ ├── ids.mlt │ │ ├── ids64.json │ │ ├── ids64.mlt │ │ ├── ids64_delta.json │ │ ├── ids64_delta.mlt │ │ ├── ids64_delta_rle.json │ │ ├── ids64_delta_rle.mlt │ │ ├── ids64_opt.json │ │ ├── ids64_opt.mlt │ │ ├── ids64_opt_delta.json │ │ ├── ids64_opt_delta.mlt │ │ ├── ids64_rle.json │ │ ├── ids64_rle.mlt │ │ ├── ids_delta.json │ │ ├── ids_delta.mlt │ │ ├── ids_delta_rle.json │ │ ├── ids_delta_rle.mlt │ │ ├── ids_opt.json │ │ ├── ids_opt.mlt │ │ ├── ids_opt_delta.json │ │ ├── ids_opt_delta.mlt │ │ ├── ids_rle.json │ │ ├── ids_rle.mlt │ │ ├── line.json │ │ ├── line.mlt │ │ ├── line_morton_curve_morton.json │ │ ├── line_morton_curve_morton.mlt │ │ ├── line_morton_curve_no_morton.json │ │ ├── line_morton_curve_no_morton.mlt │ │ ├── line_zero_length.json │ │ ├── line_zero_length.mlt │ │ ├── mix_2_line_line.json │ │ ├── mix_2_line_line.mlt │ │ ├── mix_2_line_mline.json │ │ ├── mix_2_line_mline.mlt │ │ ├── mix_2_line_mpoly.json │ │ ├── mix_2_line_mpoly.mlt │ │ ├── mix_2_line_mpt.json │ │ ├── mix_2_line_mpt.mlt │ │ ├── mix_2_line_poly.json │ │ ├── mix_2_line_poly.mlt │ │ ├── mix_2_line_polyh.json │ │ ├── mix_2_line_polyh.mlt │ │ ├── mix_2_mline_mline.json │ │ ├── mix_2_mline_mline.mlt │ │ ├── mix_2_mline_mpoly.json │ │ ├── mix_2_mline_mpoly.mlt │ │ ├── mix_2_mpoly_mpoly.json │ │ ├── mix_2_mpoly_mpoly.mlt │ │ ├── mix_2_mpoly_mpoly_tes.json │ │ ├── mix_2_mpoly_mpoly_tes.mlt │ │ ├── mix_2_mpt_mline.json │ │ ├── mix_2_mpt_mline.mlt │ │ ├── mix_2_mpt_mpoly.json │ │ ├── mix_2_mpt_mpoly.mlt │ │ ├── mix_2_mpt_mpt.json │ │ ├── mix_2_mpt_mpt.mlt │ │ ├── mix_2_poly_mline.json │ │ ├── mix_2_poly_mline.mlt │ │ ├── mix_2_poly_mpoly.json │ │ ├── mix_2_poly_mpoly.mlt │ │ ├── mix_2_poly_mpoly_tes.json │ │ ├── mix_2_poly_mpoly_tes.mlt │ │ ├── mix_2_poly_mpt.json │ │ ├── mix_2_poly_mpt.mlt │ │ ├── mix_2_poly_poly.json │ │ ├── mix_2_poly_poly.mlt │ │ ├── mix_2_poly_poly_tes.json │ │ ├── mix_2_poly_poly_tes.mlt │ │ ├── mix_2_poly_polyh.json │ │ ├── mix_2_poly_polyh.mlt │ │ ├── mix_2_poly_polyh_tes.json │ │ ├── mix_2_poly_polyh_tes.mlt │ │ ├── mix_2_polyh_mline.json │ │ ├── mix_2_polyh_mline.mlt │ │ ├── mix_2_polyh_mpoly.json │ │ ├── mix_2_polyh_mpoly.mlt │ │ ├── mix_2_polyh_mpoly_tes.json │ │ ├── mix_2_polyh_mpoly_tes.mlt │ │ ├── mix_2_polyh_mpt.json │ │ ├── mix_2_polyh_mpt.mlt │ │ ├── mix_2_polyh_polyh.json │ │ ├── mix_2_polyh_polyh.mlt │ │ ├── mix_2_polyh_polyh_tes.json │ │ ├── mix_2_polyh_polyh_tes.mlt │ │ ├── mix_2_pt_line.json │ │ ├── mix_2_pt_line.mlt │ │ ├── mix_2_pt_mline.json │ │ ├── mix_2_pt_mline.mlt │ │ ├── mix_2_pt_mpoly.json │ │ ├── mix_2_pt_mpoly.mlt │ │ ├── mix_2_pt_mpt.json │ │ ├── mix_2_pt_mpt.mlt │ │ ├── mix_2_pt_poly.json │ │ ├── mix_2_pt_poly.mlt │ │ ├── mix_2_pt_polyh.json │ │ ├── mix_2_pt_polyh.mlt │ │ ├── mix_2_pt_pt.json │ │ ├── mix_2_pt_pt.mlt │ │ ├── mix_3_line_mline_line.json │ │ ├── mix_3_line_mline_line.mlt │ │ ├── mix_3_line_mline_mpoly.json │ │ ├── mix_3_line_mline_mpoly.mlt │ │ ├── mix_3_line_mpoly_line.json │ │ ├── mix_3_line_mpoly_line.mlt │ │ ├── mix_3_line_mpt_line.json │ │ ├── mix_3_line_mpt_line.mlt │ │ ├── mix_3_line_mpt_mline.json │ │ ├── mix_3_line_mpt_mline.mlt │ │ ├── mix_3_line_mpt_mpoly.json │ │ ├── mix_3_line_mpt_mpoly.mlt │ │ ├── mix_3_line_poly_line.json │ │ ├── mix_3_line_poly_line.mlt │ │ ├── mix_3_line_poly_mline.json │ │ ├── mix_3_line_poly_mline.mlt │ │ ├── mix_3_line_poly_mpoly.json │ │ ├── mix_3_line_poly_mpoly.mlt │ │ ├── mix_3_line_poly_mpt.json │ │ ├── mix_3_line_poly_mpt.mlt │ │ ├── mix_3_line_poly_polyh.json │ │ ├── mix_3_line_poly_polyh.mlt │ │ ├── mix_3_line_polyh_line.json │ │ ├── mix_3_line_polyh_line.mlt │ │ ├── mix_3_line_polyh_mline.json │ │ ├── mix_3_line_polyh_mline.mlt │ │ ├── mix_3_line_polyh_mpoly.json │ │ ├── mix_3_line_polyh_mpoly.mlt │ │ ├── mix_3_line_polyh_mpt.json │ │ ├── mix_3_line_polyh_mpt.mlt │ │ ├── mix_3_line_pt_line.json │ │ ├── mix_3_line_pt_line.mlt │ │ ├── mix_3_mline_line_mline.json │ │ ├── mix_3_mline_line_mline.mlt │ │ ├── mix_3_mline_mpoly_mline.json │ │ ├── mix_3_mline_mpoly_mline.mlt │ │ ├── mix_3_mline_mpt_mline.json │ │ ├── mix_3_mline_mpt_mline.mlt │ │ ├── mix_3_mline_poly_mline.json │ │ ├── mix_3_mline_poly_mline.mlt │ │ ├── mix_3_mline_polyh_mline.json │ │ ├── mix_3_mline_polyh_mline.mlt │ │ ├── mix_3_mline_pt_mline.json │ │ ├── mix_3_mline_pt_mline.mlt │ │ ├── mix_3_mpoly_line_mpoly.json │ │ ├── mix_3_mpoly_line_mpoly.mlt │ │ ├── mix_3_mpoly_mline_mpoly.json │ │ ├── mix_3_mpoly_mline_mpoly.mlt │ │ ├── mix_3_mpoly_mpt_mpoly.json │ │ ├── mix_3_mpoly_mpt_mpoly.mlt │ │ ├── mix_3_mpoly_poly_mpoly.json │ │ ├── mix_3_mpoly_poly_mpoly.mlt │ │ ├── mix_3_mpoly_poly_mpoly_tes.json │ │ ├── mix_3_mpoly_poly_mpoly_tes.mlt │ │ ├── mix_3_mpoly_polyh_mpoly.json │ │ ├── mix_3_mpoly_polyh_mpoly.mlt │ │ ├── mix_3_mpoly_polyh_mpoly_tes.json │ │ ├── mix_3_mpoly_polyh_mpoly_tes.mlt │ │ ├── mix_3_mpoly_pt_mpoly.json │ │ ├── mix_3_mpoly_pt_mpoly.mlt │ │ ├── mix_3_mpt_line_mpt.json │ │ ├── mix_3_mpt_line_mpt.mlt │ │ ├── mix_3_mpt_mline_mpoly.json │ │ ├── mix_3_mpt_mline_mpoly.mlt │ │ ├── mix_3_mpt_mline_mpt.json │ │ ├── mix_3_mpt_mline_mpt.mlt │ │ ├── mix_3_mpt_mpoly_mpt.json │ │ ├── mix_3_mpt_mpoly_mpt.mlt │ │ ├── mix_3_mpt_poly_mpt.json │ │ ├── mix_3_mpt_poly_mpt.mlt │ │ ├── mix_3_mpt_polyh_mpt.json │ │ ├── mix_3_mpt_polyh_mpt.mlt │ │ ├── mix_3_mpt_pt_mpt.json │ │ ├── mix_3_mpt_pt_mpt.mlt │ │ ├── mix_3_poly_line_poly.json │ │ ├── mix_3_poly_line_poly.mlt │ │ ├── mix_3_poly_mline_mpoly.json │ │ ├── mix_3_poly_mline_mpoly.mlt │ │ ├── mix_3_poly_mline_poly.json │ │ ├── mix_3_poly_mline_poly.mlt │ │ ├── mix_3_poly_mpoly_poly.json │ │ ├── mix_3_poly_mpoly_poly.mlt │ │ ├── mix_3_poly_mpoly_poly_tes.json │ │ ├── mix_3_poly_mpoly_poly_tes.mlt │ │ ├── mix_3_poly_mpt_mline.json │ │ ├── mix_3_poly_mpt_mline.mlt │ │ ├── mix_3_poly_mpt_mpoly.json │ │ ├── mix_3_poly_mpt_mpoly.mlt │ │ ├── mix_3_poly_mpt_poly.json │ │ ├── mix_3_poly_mpt_poly.mlt │ │ ├── mix_3_poly_polyh_mline.json │ │ ├── mix_3_poly_polyh_mline.mlt │ │ ├── mix_3_poly_polyh_mpoly.json │ │ ├── mix_3_poly_polyh_mpoly.mlt │ │ ├── mix_3_poly_polyh_mpoly_tes.json │ │ ├── mix_3_poly_polyh_mpoly_tes.mlt │ │ ├── mix_3_poly_polyh_mpt.json │ │ ├── mix_3_poly_polyh_mpt.mlt │ │ ├── mix_3_poly_polyh_poly.json │ │ ├── mix_3_poly_polyh_poly.mlt │ │ ├── mix_3_poly_polyh_poly_tes.json │ │ ├── mix_3_poly_polyh_poly_tes.mlt │ │ ├── mix_3_poly_pt_poly.json │ │ ├── mix_3_poly_pt_poly.mlt │ │ ├── mix_3_polyh_line_polyh.json │ │ ├── mix_3_polyh_line_polyh.mlt │ │ ├── mix_3_polyh_mline_mpoly.json │ │ ├── mix_3_polyh_mline_mpoly.mlt │ │ ├── mix_3_polyh_mline_polyh.json │ │ ├── mix_3_polyh_mline_polyh.mlt │ │ ├── mix_3_polyh_mpoly_polyh.json │ │ ├── mix_3_polyh_mpoly_polyh.mlt │ │ ├── mix_3_polyh_mpoly_polyh_tes.json │ │ ├── mix_3_polyh_mpoly_polyh_tes.mlt │ │ ├── mix_3_polyh_mpt_mline.json │ │ ├── mix_3_polyh_mpt_mline.mlt │ │ ├── mix_3_polyh_mpt_mpoly.json │ │ ├── mix_3_polyh_mpt_mpoly.mlt │ │ ├── mix_3_polyh_mpt_polyh.json │ │ ├── mix_3_polyh_mpt_polyh.mlt │ │ ├── mix_3_polyh_poly_polyh.json │ │ ├── mix_3_polyh_poly_polyh.mlt │ │ ├── mix_3_polyh_poly_polyh_tes.json │ │ ├── mix_3_polyh_poly_polyh_tes.mlt │ │ ├── mix_3_polyh_pt_polyh.json │ │ ├── mix_3_polyh_pt_polyh.mlt │ │ ├── mix_3_pt_line_mline.json │ │ ├── mix_3_pt_line_mline.mlt │ │ ├── mix_3_pt_line_mpoly.json │ │ ├── mix_3_pt_line_mpoly.mlt │ │ ├── mix_3_pt_line_mpt.json │ │ ├── mix_3_pt_line_mpt.mlt │ │ ├── mix_3_pt_line_poly.json │ │ ├── mix_3_pt_line_poly.mlt │ │ ├── mix_3_pt_line_polyh.json │ │ ├── mix_3_pt_line_polyh.mlt │ │ ├── mix_3_pt_line_pt.json │ │ ├── mix_3_pt_line_pt.mlt │ │ ├── mix_3_pt_mline_mpoly.json │ │ ├── mix_3_pt_mline_mpoly.mlt │ │ ├── mix_3_pt_mline_pt.json │ │ ├── mix_3_pt_mline_pt.mlt │ │ ├── mix_3_pt_mpoly_pt.json │ │ ├── mix_3_pt_mpoly_pt.mlt │ │ ├── mix_3_pt_mpt_mline.json │ │ ├── mix_3_pt_mpt_mline.mlt │ │ ├── mix_3_pt_mpt_mpoly.json │ │ ├── mix_3_pt_mpt_mpoly.mlt │ │ ├── mix_3_pt_mpt_pt.json │ │ ├── mix_3_pt_mpt_pt.mlt │ │ ├── mix_3_pt_poly_mline.json │ │ ├── mix_3_pt_poly_mline.mlt │ │ ├── mix_3_pt_poly_mpoly.json │ │ ├── mix_3_pt_poly_mpoly.mlt │ │ ├── mix_3_pt_poly_mpt.json │ │ ├── mix_3_pt_poly_mpt.mlt │ │ ├── mix_3_pt_poly_polyh.json │ │ ├── mix_3_pt_poly_polyh.mlt │ │ ├── mix_3_pt_poly_pt.json │ │ ├── mix_3_pt_poly_pt.mlt │ │ ├── mix_3_pt_polyh_mline.json │ │ ├── mix_3_pt_polyh_mline.mlt │ │ ├── mix_3_pt_polyh_mpoly.json │ │ ├── mix_3_pt_polyh_mpoly.mlt │ │ ├── mix_3_pt_polyh_mpt.json │ │ ├── mix_3_pt_polyh_mpt.mlt │ │ ├── mix_3_pt_polyh_pt.json │ │ ├── mix_3_pt_polyh_pt.mlt │ │ ├── mix_4_line_mpt_mline_mpoly.json │ │ ├── mix_4_line_mpt_mline_mpoly.mlt │ │ ├── mix_4_line_poly_mline_mpoly.json │ │ ├── mix_4_line_poly_mline_mpoly.mlt │ │ ├── mix_4_line_poly_mpt_mline.json │ │ ├── mix_4_line_poly_mpt_mline.mlt │ │ ├── mix_4_line_poly_mpt_mpoly.json │ │ ├── mix_4_line_poly_mpt_mpoly.mlt │ │ ├── mix_4_line_poly_polyh_mline.json │ │ ├── mix_4_line_poly_polyh_mline.mlt │ │ ├── mix_4_line_poly_polyh_mpoly.json │ │ ├── mix_4_line_poly_polyh_mpoly.mlt │ │ ├── mix_4_line_poly_polyh_mpt.json │ │ ├── mix_4_line_poly_polyh_mpt.mlt │ │ ├── mix_4_line_polyh_mline_mpoly.json │ │ ├── mix_4_line_polyh_mline_mpoly.mlt │ │ ├── mix_4_line_polyh_mpt_mline.json │ │ ├── mix_4_line_polyh_mpt_mline.mlt │ │ ├── mix_4_line_polyh_mpt_mpoly.json │ │ ├── mix_4_line_polyh_mpt_mpoly.mlt │ │ ├── mix_4_poly_mpt_mline_mpoly.json │ │ ├── mix_4_poly_mpt_mline_mpoly.mlt │ │ ├── mix_4_poly_polyh_mline_mpoly.json │ │ ├── mix_4_poly_polyh_mline_mpoly.mlt │ │ ├── mix_4_poly_polyh_mpt_mline.json │ │ ├── mix_4_poly_polyh_mpt_mline.mlt │ │ ├── mix_4_poly_polyh_mpt_mpoly.json │ │ ├── mix_4_poly_polyh_mpt_mpoly.mlt │ │ ├── mix_4_polyh_mpt_mline_mpoly.json │ │ ├── mix_4_polyh_mpt_mline_mpoly.mlt │ │ ├── mix_4_pt_line_mline_mpoly.json │ │ ├── mix_4_pt_line_mline_mpoly.mlt │ │ ├── mix_4_pt_line_mpt_mline.json │ │ ├── mix_4_pt_line_mpt_mline.mlt │ │ ├── mix_4_pt_line_mpt_mpoly.json │ │ ├── mix_4_pt_line_mpt_mpoly.mlt │ │ ├── mix_4_pt_line_poly_mline.json │ │ ├── mix_4_pt_line_poly_mline.mlt │ │ ├── mix_4_pt_line_poly_mpoly.json │ │ ├── mix_4_pt_line_poly_mpoly.mlt │ │ ├── mix_4_pt_line_poly_mpt.json │ │ ├── mix_4_pt_line_poly_mpt.mlt │ │ ├── mix_4_pt_line_poly_polyh.json │ │ ├── mix_4_pt_line_poly_polyh.mlt │ │ ├── mix_4_pt_line_polyh_mline.json │ │ ├── mix_4_pt_line_polyh_mline.mlt │ │ ├── mix_4_pt_line_polyh_mpoly.json │ │ ├── mix_4_pt_line_polyh_mpoly.mlt │ │ ├── mix_4_pt_line_polyh_mpt.json │ │ ├── mix_4_pt_line_polyh_mpt.mlt │ │ ├── mix_4_pt_mpt_mline_mpoly.json │ │ ├── mix_4_pt_mpt_mline_mpoly.mlt │ │ ├── mix_4_pt_poly_mline_mpoly.json │ │ ├── mix_4_pt_poly_mline_mpoly.mlt │ │ ├── mix_4_pt_poly_mpt_mline.json │ │ ├── mix_4_pt_poly_mpt_mline.mlt │ │ ├── mix_4_pt_poly_mpt_mpoly.json │ │ ├── mix_4_pt_poly_mpt_mpoly.mlt │ │ ├── mix_4_pt_poly_polyh_mline.json │ │ ├── mix_4_pt_poly_polyh_mline.mlt │ │ ├── mix_4_pt_poly_polyh_mpoly.json │ │ ├── mix_4_pt_poly_polyh_mpoly.mlt │ │ ├── mix_4_pt_poly_polyh_mpt.json │ │ ├── mix_4_pt_poly_polyh_mpt.mlt │ │ ├── mix_4_pt_polyh_mline_mpoly.json │ │ ├── mix_4_pt_polyh_mline_mpoly.mlt │ │ ├── mix_4_pt_polyh_mpt_mline.json │ │ ├── mix_4_pt_polyh_mpt_mline.mlt │ │ ├── mix_4_pt_polyh_mpt_mpoly.json │ │ ├── mix_4_pt_polyh_mpt_mpoly.mlt │ │ ├── mix_5_line_poly_mpt_mline_mpoly.json │ │ ├── mix_5_line_poly_mpt_mline_mpoly.mlt │ │ ├── mix_5_line_poly_polyh_mline_mpoly.json │ │ ├── mix_5_line_poly_polyh_mline_mpoly.mlt │ │ ├── mix_5_line_poly_polyh_mpt_mline.json │ │ ├── mix_5_line_poly_polyh_mpt_mline.mlt │ │ ├── mix_5_line_poly_polyh_mpt_mpoly.json │ │ ├── mix_5_line_poly_polyh_mpt_mpoly.mlt │ │ ├── mix_5_line_polyh_mpt_mline_mpoly.json │ │ ├── mix_5_line_polyh_mpt_mline_mpoly.mlt │ │ ├── mix_5_poly_polyh_mpt_mline_mpoly.json │ │ ├── mix_5_poly_polyh_mpt_mline_mpoly.mlt │ │ ├── mix_5_pt_line_mpt_mline_mpoly.json │ │ ├── mix_5_pt_line_mpt_mline_mpoly.mlt │ │ ├── mix_5_pt_line_poly_mline_mpoly.json │ │ ├── mix_5_pt_line_poly_mline_mpoly.mlt │ │ ├── mix_5_pt_line_poly_mpt_mline.json │ │ ├── mix_5_pt_line_poly_mpt_mline.mlt │ │ ├── mix_5_pt_line_poly_mpt_mpoly.json │ │ ├── mix_5_pt_line_poly_mpt_mpoly.mlt │ │ ├── mix_5_pt_line_poly_polyh_mline.json │ │ ├── mix_5_pt_line_poly_polyh_mline.mlt │ │ ├── mix_5_pt_line_poly_polyh_mpoly.json │ │ ├── mix_5_pt_line_poly_polyh_mpoly.mlt │ │ ├── mix_5_pt_line_poly_polyh_mpt.json │ │ ├── mix_5_pt_line_poly_polyh_mpt.mlt │ │ ├── mix_5_pt_line_polyh_mline_mpoly.json │ │ ├── mix_5_pt_line_polyh_mline_mpoly.mlt │ │ ├── mix_5_pt_line_polyh_mpt_mline.json │ │ ├── mix_5_pt_line_polyh_mpt_mline.mlt │ │ ├── mix_5_pt_line_polyh_mpt_mpoly.json │ │ ├── mix_5_pt_line_polyh_mpt_mpoly.mlt │ │ ├── mix_5_pt_poly_mpt_mline_mpoly.json │ │ ├── mix_5_pt_poly_mpt_mline_mpoly.mlt │ │ ├── mix_5_pt_poly_polyh_mline_mpoly.json │ │ ├── mix_5_pt_poly_polyh_mline_mpoly.mlt │ │ ├── mix_5_pt_poly_polyh_mpt_mline.json │ │ ├── mix_5_pt_poly_polyh_mpt_mline.mlt │ │ ├── mix_5_pt_poly_polyh_mpt_mpoly.json │ │ ├── mix_5_pt_poly_polyh_mpt_mpoly.mlt │ │ ├── mix_5_pt_polyh_mpt_mline_mpoly.json │ │ ├── mix_5_pt_polyh_mpt_mline_mpoly.mlt │ │ ├── mix_6_line_poly_polyh_mpt_mline_mpoly.json │ │ ├── mix_6_line_poly_polyh_mpt_mline_mpoly.mlt │ │ ├── mix_6_pt_line_poly_mpt_mline_mpoly.json │ │ ├── mix_6_pt_line_poly_mpt_mline_mpoly.mlt │ │ ├── mix_6_pt_line_poly_polyh_mline_mpoly.json │ │ ├── mix_6_pt_line_poly_polyh_mline_mpoly.mlt │ │ ├── mix_6_pt_line_poly_polyh_mpt_mline.json │ │ ├── mix_6_pt_line_poly_polyh_mpt_mline.mlt │ │ ├── mix_6_pt_line_poly_polyh_mpt_mpoly.json │ │ ├── mix_6_pt_line_poly_polyh_mpt_mpoly.mlt │ │ ├── mix_6_pt_line_polyh_mpt_mline_mpoly.json │ │ ├── mix_6_pt_line_polyh_mpt_mline_mpoly.mlt │ │ ├── mix_6_pt_poly_polyh_mpt_mline_mpoly.json │ │ ├── mix_6_pt_poly_polyh_mpt_mline_mpoly.mlt │ │ ├── mix_7_pt_line_poly_polyh_mpt_mline_mpoly.json │ │ ├── mix_7_pt_line_poly_polyh_mpt_mline_mpoly.mlt │ │ ├── multiline.json │ │ ├── multiline.mlt │ │ ├── multiline_morton.json │ │ ├── multiline_morton.mlt │ │ ├── multipoint.json │ │ ├── multipoint.mlt │ │ ├── multipoint_morton.json │ │ ├── multipoint_morton.mlt │ │ ├── point.json │ │ ├── point.mlt │ │ ├── poly.json │ │ ├── poly.mlt │ │ ├── poly_collinear.json │ │ ├── poly_collinear.mlt │ │ ├── poly_collinear_fpf.json │ │ ├── poly_collinear_fpf.mlt │ │ ├── poly_collinear_fpf_tes.json │ │ ├── poly_collinear_fpf_tes.mlt │ │ ├── poly_collinear_tes.json │ │ ├── poly_collinear_tes.mlt │ │ ├── poly_fpf.json │ │ ├── poly_fpf.mlt │ │ ├── poly_fpf_tes.json │ │ ├── poly_fpf_tes.mlt │ │ ├── poly_hole.json │ │ ├── poly_hole.mlt │ │ ├── poly_hole_fpf.json │ │ ├── poly_hole_fpf.mlt │ │ ├── poly_hole_fpf_tes.json │ │ ├── poly_hole_fpf_tes.mlt │ │ ├── poly_hole_tes.json │ │ ├── poly_hole_tes.mlt │ │ ├── poly_hole_touching.json │ │ ├── poly_hole_touching.mlt │ │ ├── poly_hole_touching_fpf.json │ │ ├── poly_hole_touching_fpf.mlt │ │ ├── poly_hole_touching_fpf_tes.json │ │ ├── poly_hole_touching_fpf_tes.mlt │ │ ├── poly_hole_touching_tes.json │ │ ├── poly_hole_touching_tes.mlt │ │ ├── poly_morton_hole_morton.json │ │ ├── poly_morton_hole_morton.mlt │ │ ├── poly_morton_ring_morton.json │ │ ├── poly_morton_ring_morton.mlt │ │ ├── poly_morton_ring_no_morton.json │ │ ├── poly_morton_ring_no_morton.mlt │ │ ├── poly_multi.json │ │ ├── poly_multi.mlt │ │ ├── poly_multi_fpf.json │ │ ├── poly_multi_fpf.mlt │ │ ├── poly_multi_fpf_tes.json │ │ ├── poly_multi_fpf_tes.mlt │ │ ├── poly_multi_morton_hole_morton.json │ │ ├── poly_multi_morton_hole_morton.mlt │ │ ├── poly_multi_morton_ring_morton.json │ │ ├── poly_multi_morton_ring_morton.mlt │ │ ├── poly_multi_morton_ring_no_morton.json │ │ ├── poly_multi_morton_ring_no_morton.mlt │ │ ├── poly_multi_tes.json │ │ ├── poly_multi_tes.mlt │ │ ├── poly_self_intersect.json │ │ ├── poly_self_intersect.mlt │ │ ├── poly_self_intersect_fpf.json │ │ ├── poly_self_intersect_fpf.mlt │ │ ├── poly_self_intersect_fpf_tes.json │ │ ├── poly_self_intersect_fpf_tes.mlt │ │ ├── poly_self_intersect_tes.json │ │ ├── poly_self_intersect_tes.mlt │ │ ├── poly_tes.json │ │ ├── poly_tes.mlt │ │ ├── prop_bool.json │ │ ├── prop_bool.mlt │ │ ├── prop_bool_false.json │ │ ├── prop_bool_false.mlt │ │ ├── prop_bool_false_null.json │ │ ├── prop_bool_false_null.mlt │ │ ├── prop_bool_null_false.json │ │ ├── prop_bool_null_false.mlt │ │ ├── prop_bool_null_true.json │ │ ├── prop_bool_null_true.mlt │ │ ├── prop_bool_true_null.json │ │ ├── prop_bool_true_null.mlt │ │ ├── prop_empty_name.json │ │ ├── prop_empty_name.mlt │ │ ├── prop_f32.json │ │ ├── prop_f32.mlt │ │ ├── prop_f32_max.json │ │ ├── prop_f32_max.mlt │ │ ├── prop_f32_min_norm.json │ │ ├── prop_f32_min_norm.mlt │ │ ├── prop_f32_min_val.json │ │ ├── prop_f32_min_val.mlt │ │ ├── prop_f32_nan.json │ │ ├── prop_f32_nan.mlt │ │ ├── prop_f32_neg_inf.json │ │ ├── prop_f32_neg_inf.mlt │ │ ├── prop_f32_neg_zero.json │ │ ├── prop_f32_neg_zero.mlt │ │ ├── prop_f32_null_val.json │ │ ├── prop_f32_null_val.mlt │ │ ├── prop_f32_pos_inf.json │ │ ├── prop_f32_pos_inf.mlt │ │ ├── prop_f32_val_null.json │ │ ├── prop_f32_val_null.mlt │ │ ├── prop_f32_zero.json │ │ ├── prop_f32_zero.mlt │ │ ├── prop_f64.json │ │ ├── prop_f64.mlt │ │ ├── prop_f64_max.json │ │ ├── prop_f64_max.mlt │ │ ├── prop_f64_min_norm.json │ │ ├── prop_f64_min_norm.mlt │ │ ├── prop_f64_min_val.json │ │ ├── prop_f64_min_val.mlt │ │ ├── prop_f64_nan.json │ │ ├── prop_f64_nan.mlt │ │ ├── prop_f64_neg_inf.json │ │ ├── prop_f64_neg_inf.mlt │ │ ├── prop_f64_neg_zero.json │ │ ├── prop_f64_neg_zero.mlt │ │ ├── prop_f64_null_val.json │ │ ├── prop_f64_null_val.mlt │ │ ├── prop_f64_pos_inf.json │ │ ├── prop_f64_pos_inf.mlt │ │ ├── prop_f64_val_null.json │ │ ├── prop_f64_val_null.mlt │ │ ├── prop_f64_zero.json │ │ ├── prop_f64_zero.mlt │ │ ├── prop_i32.json │ │ ├── prop_i32.mlt │ │ ├── prop_i32_max.json │ │ ├── prop_i32_max.mlt │ │ ├── prop_i32_min.json │ │ ├── prop_i32_min.mlt │ │ ├── prop_i32_neg.json │ │ ├── prop_i32_neg.mlt │ │ ├── prop_i32_null_val.json │ │ ├── prop_i32_null_val.mlt │ │ ├── prop_i32_val_null.json │ │ ├── prop_i32_val_null.mlt │ │ ├── prop_i64.json │ │ ├── prop_i64.mlt │ │ ├── prop_i64_max.json │ │ ├── prop_i64_max.mlt │ │ ├── prop_i64_min.json │ │ ├── prop_i64_min.mlt │ │ ├── prop_i64_neg.json │ │ ├── prop_i64_neg.mlt │ │ ├── prop_i64_null_val.json │ │ ├── prop_i64_null_val.mlt │ │ ├── prop_i64_val_null.json │ │ ├── prop_i64_val_null.mlt │ │ ├── prop_special_name.json │ │ ├── prop_special_name.mlt │ │ ├── prop_str_ascii.json │ │ ├── prop_str_ascii.mlt │ │ ├── prop_str_empty.json │ │ ├── prop_str_empty.mlt │ │ ├── prop_str_empty_val.json │ │ ├── prop_str_empty_val.mlt │ │ ├── prop_str_escape.json │ │ ├── prop_str_escape.mlt │ │ ├── prop_str_null_val.json │ │ ├── prop_str_null_val.mlt │ │ ├── prop_str_special.json │ │ ├── prop_str_special.mlt │ │ ├── prop_str_unicode.json │ │ ├── prop_str_unicode.mlt │ │ ├── prop_str_val_empty.json │ │ ├── prop_str_val_empty.mlt │ │ ├── prop_str_val_null.json │ │ ├── prop_str_val_null.mlt │ │ ├── prop_u32.json │ │ ├── prop_u32.mlt │ │ ├── prop_u32_max.json │ │ ├── prop_u32_max.mlt │ │ ├── prop_u32_min.json │ │ ├── prop_u32_min.mlt │ │ ├── prop_u32_null_val.json │ │ ├── prop_u32_null_val.mlt │ │ ├── prop_u32_val_null.json │ │ ├── prop_u32_val_null.mlt │ │ ├── prop_u64.json │ │ ├── prop_u64.mlt │ │ ├── prop_u64_max.json │ │ ├── prop_u64_max.mlt │ │ ├── prop_u64_min.json │ │ ├── prop_u64_min.mlt │ │ ├── prop_u64_null_val.json │ │ ├── prop_u64_null_val.mlt │ │ ├── prop_u64_val_null.json │ │ ├── prop_u64_val_null.mlt │ │ ├── props_i32.json │ │ ├── props_i32.mlt │ │ ├── props_i32_delta.json │ │ ├── props_i32_delta.mlt │ │ ├── props_i32_delta_rle.json │ │ ├── props_i32_delta_rle.mlt │ │ ├── props_i32_rle.json │ │ ├── props_i32_rle.mlt │ │ ├── props_i64.json │ │ ├── props_i64.mlt │ │ ├── props_i64_delta.json │ │ ├── props_i64_delta.mlt │ │ ├── props_i64_delta_rle.json │ │ ├── props_i64_delta_rle.mlt │ │ ├── props_i64_rle.json │ │ ├── props_i64_rle.mlt │ │ ├── props_mixed.json │ │ ├── props_mixed.mlt │ │ ├── props_no_shared_dict.json │ │ ├── props_no_shared_dict.mlt │ │ ├── props_offset_str.json │ │ ├── props_offset_str.mlt │ │ ├── props_offset_str_fsst.json │ │ ├── props_offset_str_fsst.mlt │ │ ├── props_shared_dict.json │ │ ├── props_shared_dict.mlt │ │ ├── props_shared_dict_2_same_prefix.json │ │ ├── props_shared_dict_2_same_prefix.mlt │ │ ├── props_shared_dict_fsst.json │ │ ├── props_shared_dict_fsst.mlt │ │ ├── props_shared_dict_no_child_name.json │ │ ├── props_shared_dict_no_child_name.mlt │ │ ├── props_shared_dict_no_child_name_fsst.json │ │ ├── props_shared_dict_no_child_name_fsst.mlt │ │ ├── props_shared_dict_no_struct_name.json │ │ ├── props_shared_dict_no_struct_name.mlt │ │ ├── props_shared_dict_no_struct_name_fsst.json │ │ ├── props_shared_dict_no_struct_name_fsst.mlt │ │ ├── props_shared_dict_one_child.json │ │ ├── props_shared_dict_one_child.mlt │ │ ├── props_shared_dict_one_child_fsst.json │ │ ├── props_shared_dict_one_child_fsst.mlt │ │ ├── props_str.json │ │ ├── props_str.mlt │ │ ├── props_str_fsst.json │ │ ├── props_str_fsst.mlt │ │ ├── props_u32.json │ │ ├── props_u32.mlt │ │ ├── props_u32_delta.json │ │ ├── props_u32_delta.mlt │ │ ├── props_u32_delta_rle.json │ │ ├── props_u32_delta_rle.mlt │ │ ├── props_u32_fpf_127.json │ │ ├── props_u32_fpf_127.mlt │ │ ├── props_u32_fpf_128.json │ │ ├── props_u32_fpf_128.mlt │ │ ├── props_u32_fpf_129.json │ │ ├── props_u32_fpf_129.mlt │ │ ├── props_u32_fpf_255.json │ │ ├── props_u32_fpf_255.mlt │ │ ├── props_u32_fpf_256.json │ │ ├── props_u32_fpf_256.mlt │ │ ├── props_u32_fpf_257.json │ │ ├── props_u32_fpf_257.mlt │ │ ├── props_u32_fpf_383.json │ │ ├── props_u32_fpf_383.mlt │ │ ├── props_u32_fpf_384.json │ │ ├── props_u32_fpf_384.mlt │ │ ├── props_u32_fpf_385.json │ │ ├── props_u32_fpf_385.mlt │ │ ├── props_u32_fpf_511.json │ │ ├── props_u32_fpf_511.mlt │ │ ├── props_u32_fpf_512.json │ │ ├── props_u32_fpf_512.mlt │ │ ├── props_u32_fpf_513.json │ │ ├── props_u32_fpf_513.mlt │ │ ├── props_u32_rle.json │ │ ├── props_u32_rle.mlt │ │ ├── props_u64.json │ │ ├── props_u64.mlt │ │ ├── props_u64_delta.json │ │ ├── props_u64_delta.mlt │ │ ├── props_u64_delta_rle.json │ │ ├── props_u64_delta_rle.mlt │ │ ├── props_u64_rle.json │ │ └── props_u64_rle.mlt │ ├── 0x01-rust/ │ │ ├── id64_max.json │ │ ├── id64_max.mlt │ │ ├── id_max.json │ │ ├── id_max.mlt │ │ ├── ids64_minmax.json │ │ ├── ids64_minmax.mlt │ │ ├── ids64_minmax_delta.json │ │ ├── ids64_minmax_delta.mlt │ │ ├── ids_delta_fpf.json │ │ ├── ids_delta_fpf.mlt │ │ ├── ids_fpf.json │ │ ├── ids_fpf.mlt │ │ ├── mix_2_poly_poly_tes_ns.json │ │ ├── mix_2_poly_poly_tes_ns.mlt │ │ ├── mix_2_poly_polyh_tes_ns.json │ │ ├── mix_2_poly_polyh_tes_ns.mlt │ │ ├── mix_2_polyh_polyh_tes_ns.json │ │ ├── mix_2_polyh_polyh_tes_ns.mlt │ │ ├── mix_3_poly_polyh_poly_tes_ns.json │ │ ├── mix_3_poly_polyh_poly_tes_ns.mlt │ │ ├── mix_3_polyh_poly_polyh_tes_ns.json │ │ ├── mix_3_polyh_poly_polyh_tes_ns.mlt │ │ ├── poly_collinear_fpf_tes_ns.json │ │ ├── poly_collinear_fpf_tes_ns.mlt │ │ ├── poly_collinear_tes_ns.json │ │ ├── poly_collinear_tes_ns.mlt │ │ ├── poly_fpf_tes_ns.json │ │ ├── poly_fpf_tes_ns.mlt │ │ ├── poly_hole_fpf_tes_ns.json │ │ ├── poly_hole_fpf_tes_ns.mlt │ │ ├── poly_hole_tes_ns.json │ │ ├── poly_hole_tes_ns.mlt │ │ ├── poly_hole_touching_fpf_tes_ns.json │ │ ├── poly_hole_touching_fpf_tes_ns.mlt │ │ ├── poly_hole_touching_tes_ns.json │ │ ├── poly_hole_touching_tes_ns.mlt │ │ ├── poly_self_intersect_fpf_tes_ns.json │ │ ├── poly_self_intersect_fpf_tes_ns.mlt │ │ ├── poly_self_intersect_tes_ns.json │ │ ├── poly_self_intersect_tes_ns.mlt │ │ ├── poly_tes_ns.json │ │ ├── poly_tes_ns.mlt │ │ ├── prop_bool_false_np.json │ │ ├── prop_bool_false_np.mlt │ │ ├── prop_bool_np.json │ │ ├── prop_bool_np.mlt │ │ ├── prop_empty_name_np.json │ │ ├── prop_empty_name_np.mlt │ │ ├── prop_f32_max_np.json │ │ ├── prop_f32_max_np.mlt │ │ ├── prop_f32_min_norm_np.json │ │ ├── prop_f32_min_norm_np.mlt │ │ ├── prop_f32_min_val_np.json │ │ ├── prop_f32_min_val_np.mlt │ │ ├── prop_f32_nan_np.json │ │ ├── prop_f32_nan_np.mlt │ │ ├── prop_f32_neg_inf_np.json │ │ ├── prop_f32_neg_inf_np.mlt │ │ ├── prop_f32_neg_zero_np.json │ │ ├── prop_f32_neg_zero_np.mlt │ │ ├── prop_f32_np.json │ │ ├── prop_f32_np.mlt │ │ ├── prop_f32_pos_inf_np.json │ │ ├── prop_f32_pos_inf_np.mlt │ │ ├── prop_f32_zero_np.json │ │ ├── prop_f32_zero_np.mlt │ │ ├── prop_f64_max_np.json │ │ ├── prop_f64_max_np.mlt │ │ ├── prop_f64_min_norm_np.json │ │ ├── prop_f64_min_norm_np.mlt │ │ ├── prop_f64_min_val_np.json │ │ ├── prop_f64_min_val_np.mlt │ │ ├── prop_f64_nan_np.json │ │ ├── prop_f64_nan_np.mlt │ │ ├── prop_f64_neg_inf_np.json │ │ ├── prop_f64_neg_inf_np.mlt │ │ ├── prop_f64_neg_zero_np.json │ │ ├── prop_f64_neg_zero_np.mlt │ │ ├── prop_f64_np.json │ │ ├── prop_f64_np.mlt │ │ ├── prop_f64_pos_inf_np.json │ │ ├── prop_f64_pos_inf_np.mlt │ │ ├── prop_f64_zero_np.json │ │ ├── prop_f64_zero_np.mlt │ │ ├── prop_i32_max_np.json │ │ ├── prop_i32_max_np.mlt │ │ ├── prop_i32_min_np.json │ │ ├── prop_i32_min_np.mlt │ │ ├── prop_i32_neg_np.json │ │ ├── prop_i32_neg_np.mlt │ │ ├── prop_i32_np.json │ │ ├── prop_i32_np.mlt │ │ ├── prop_i64_max_np.json │ │ ├── prop_i64_max_np.mlt │ │ ├── prop_i64_min_np.json │ │ ├── prop_i64_min_np.mlt │ │ ├── prop_i64_neg_np.json │ │ ├── prop_i64_neg_np.mlt │ │ ├── prop_i64_np.json │ │ ├── prop_i64_np.mlt │ │ ├── prop_special_name_np.json │ │ ├── prop_special_name_np.mlt │ │ ├── prop_str_ascii_np.json │ │ ├── prop_str_ascii_np.mlt │ │ ├── prop_str_empty_np.json │ │ ├── prop_str_empty_np.mlt │ │ ├── prop_str_escape_np.json │ │ ├── prop_str_escape_np.mlt │ │ ├── prop_str_special_np.json │ │ ├── prop_str_special_np.mlt │ │ ├── prop_str_unicode_np.json │ │ ├── prop_str_unicode_np.mlt │ │ ├── prop_u32_max_np.json │ │ ├── prop_u32_max_np.mlt │ │ ├── prop_u32_min_np.json │ │ ├── prop_u32_min_np.mlt │ │ ├── prop_u32_np.json │ │ ├── prop_u32_np.mlt │ │ ├── prop_u64_max_np.json │ │ ├── prop_u64_max_np.mlt │ │ ├── prop_u64_min_np.json │ │ ├── prop_u64_min_np.mlt │ │ ├── prop_u64_np.json │ │ ├── prop_u64_np.mlt │ │ ├── props_i32_delta_np.json │ │ ├── props_i32_delta_np.mlt │ │ ├── props_i32_delta_rle_np.json │ │ ├── props_i32_delta_rle_np.mlt │ │ ├── props_i32_np.json │ │ ├── props_i32_np.mlt │ │ ├── props_i32_rle_np.json │ │ ├── props_i32_rle_np.mlt │ │ ├── props_mixed_np.json │ │ ├── props_mixed_np.mlt │ │ ├── props_no_shared_dict_np.json │ │ ├── props_no_shared_dict_np.mlt │ │ ├── props_offset_str_fsst.json │ │ ├── props_offset_str_fsst.mlt │ │ ├── props_offset_str_fsst_np.json │ │ ├── props_offset_str_fsst_np.mlt │ │ ├── props_offset_str_np.json │ │ ├── props_offset_str_np.mlt │ │ ├── props_shared_dict_2_same_prefix_np.json │ │ ├── props_shared_dict_2_same_prefix_np.mlt │ │ ├── props_shared_dict_fsst.json │ │ ├── props_shared_dict_fsst.mlt │ │ ├── props_shared_dict_fsst_np.json │ │ ├── props_shared_dict_fsst_np.mlt │ │ ├── props_shared_dict_no_child_name_fsst.json │ │ ├── props_shared_dict_no_child_name_fsst.mlt │ │ ├── props_shared_dict_no_child_name_fsst_np.json │ │ ├── props_shared_dict_no_child_name_fsst_np.mlt │ │ ├── props_shared_dict_no_child_name_np.json │ │ ├── props_shared_dict_no_child_name_np.mlt │ │ ├── props_shared_dict_no_struct_name_fsst.json │ │ ├── props_shared_dict_no_struct_name_fsst.mlt │ │ ├── props_shared_dict_no_struct_name_fsst_np.json │ │ ├── props_shared_dict_no_struct_name_fsst_np.mlt │ │ ├── props_shared_dict_no_struct_name_np.json │ │ ├── props_shared_dict_no_struct_name_np.mlt │ │ ├── props_shared_dict_np.json │ │ ├── props_shared_dict_np.mlt │ │ ├── props_shared_dict_one_child_fsst.json │ │ ├── props_shared_dict_one_child_fsst.mlt │ │ ├── props_shared_dict_one_child_fsst_np.json │ │ ├── props_shared_dict_one_child_fsst_np.mlt │ │ ├── props_shared_dict_one_child_np.json │ │ ├── props_shared_dict_one_child_np.mlt │ │ ├── props_shared_dict_presence_variants.json │ │ ├── props_shared_dict_presence_variants.mlt │ │ ├── props_shared_dict_presence_variants_np.json │ │ ├── props_shared_dict_presence_variants_np.mlt │ │ ├── props_str_fsst.json │ │ ├── props_str_fsst.mlt │ │ ├── props_str_fsst_np.json │ │ ├── props_str_fsst_np.mlt │ │ ├── props_str_np.json │ │ ├── props_str_np.mlt │ │ ├── props_u32_delta_np.json │ │ ├── props_u32_delta_np.mlt │ │ ├── props_u32_delta_rle_np.json │ │ ├── props_u32_delta_rle_np.mlt │ │ ├── props_u32_fpf_127_np.json │ │ ├── props_u32_fpf_127_np.mlt │ │ ├── props_u32_fpf_128_np.json │ │ ├── props_u32_fpf_128_np.mlt │ │ ├── props_u32_fpf_129_np.json │ │ ├── props_u32_fpf_129_np.mlt │ │ ├── props_u32_fpf_255_np.json │ │ ├── props_u32_fpf_255_np.mlt │ │ ├── props_u32_fpf_256_np.json │ │ ├── props_u32_fpf_256_np.mlt │ │ ├── props_u32_fpf_257_np.json │ │ ├── props_u32_fpf_257_np.mlt │ │ ├── props_u32_fpf_383_np.json │ │ ├── props_u32_fpf_383_np.mlt │ │ ├── props_u32_fpf_384_np.json │ │ ├── props_u32_fpf_384_np.mlt │ │ ├── props_u32_fpf_385_np.json │ │ ├── props_u32_fpf_385_np.mlt │ │ ├── props_u32_fpf_511_np.json │ │ ├── props_u32_fpf_511_np.mlt │ │ ├── props_u32_fpf_512_np.json │ │ ├── props_u32_fpf_512_np.mlt │ │ ├── props_u32_fpf_513_np.json │ │ ├── props_u32_fpf_513_np.mlt │ │ ├── props_u32_np.json │ │ ├── props_u32_np.mlt │ │ ├── props_u32_rle_np.json │ │ ├── props_u32_rle_np.mlt │ │ ├── props_u64_delta_np.json │ │ ├── props_u64_delta_np.mlt │ │ ├── props_u64_delta_rle_np.json │ │ ├── props_u64_delta_rle_np.mlt │ │ ├── props_u64_np.json │ │ ├── props_u64_np.mlt │ │ ├── props_u64_rle_np.json │ │ └── props_u64_rle_np.mlt │ ├── java.txt │ ├── rust.txt │ └── synthetic-test-utils/ │ ├── index.ts │ └── package.json └── ts/ ├── .gitignore ├── .nvmrc ├── README.md ├── RELEASE.md ├── biome.json ├── buf.gen.yaml ├── eslint.config.mjs ├── mod.just ├── package.json ├── src/ │ ├── decoding/ │ │ ├── bigEndianDecode.spec.ts │ │ ├── bigEndianDecode.ts │ │ ├── decodingTestUtils.ts │ │ ├── decodingUtils.spec.ts │ │ ├── decodingUtils.ts │ │ ├── fastPforCrossLanguage.spec.ts │ │ ├── fastPforDecoder.spec.ts │ │ ├── fastPforDecoder.ts │ │ ├── fastPforShared.spec.ts │ │ ├── fastPforShared.ts │ │ ├── fastPforUnpack.spec.ts │ │ ├── fastPforUnpack.ts │ │ ├── fsstDecoder.spec.ts │ │ ├── fsstDecoder.ts │ │ ├── geometryDecoder.ts │ │ ├── geometryScaling.ts │ │ ├── intWrapper.ts │ │ ├── integerDecodingUtils.spec.ts │ │ ├── integerDecodingUtils.ts │ │ ├── integerStreamDecoder.spec.ts │ │ ├── integerStreamDecoder.ts │ │ ├── propertyDecoder.spec.ts │ │ ├── propertyDecoder.ts │ │ ├── stringDecoder.spec.ts │ │ ├── stringDecoder.ts │ │ ├── unpackNullableUtils.spec.ts │ │ └── unpackNullableUtils.ts │ ├── encoding/ │ │ ├── bigEndianEncode.ts │ │ ├── constGeometryVectorEncoder.ts │ │ ├── embeddedTilesetMetadataEncoder.ts │ │ ├── encodingUtils.ts │ │ ├── fastPforEncoder.spec.ts │ │ ├── fastPforEncoder.ts │ │ ├── fsstEncoder.ts │ │ ├── integerEncodingUtils.ts │ │ ├── integerStreamEncoder.ts │ │ ├── packNullableUtils.ts │ │ ├── propertyEncoder.ts │ │ ├── stringEncoder.ts │ │ └── zOrderCurveEncoder.ts │ ├── index.ts │ ├── metadata/ │ │ ├── tile/ │ │ │ ├── dictionaryType.ts │ │ │ ├── lengthType.ts │ │ │ ├── logicalLevelTechnique.ts │ │ │ ├── logicalStreamType.ts │ │ │ ├── offsetType.ts │ │ │ ├── physicalLevelTechnique.ts │ │ │ ├── physicalStreamType.ts │ │ │ ├── scalarType.ts │ │ │ └── streamMetadataDecoder.ts │ │ └── tileset/ │ │ ├── embeddedTilesetMetadataDecoder.spec.ts │ │ ├── embeddedTilesetMetadataDecoder.ts │ │ ├── tilesetMetadata.ts │ │ ├── typeMap.spec.ts │ │ └── typeMap.ts │ ├── mltDecoder.spec.ts │ ├── mltDecoder.ts │ ├── mltMetadata.ts │ ├── synthetic.spec.ts │ └── vector/ │ ├── constant/ │ │ ├── int32ConstVector.ts │ │ └── int64ConstVector.ts │ ├── dictionary/ │ │ └── stringDictionaryVector.ts │ ├── featureTable.ts │ ├── filter/ │ │ ├── flatSelectionVector.spec.ts │ │ ├── flatSelectionVector.ts │ │ ├── selectionVector.ts │ │ ├── selectionVectorUtil.spec.ts │ │ ├── selectionVectorUtils.ts │ │ ├── sequenceSelectionVector.spec.ts │ │ └── sequenceSelectionVector.ts │ ├── fixedSizeVector.ts │ ├── flat/ │ │ ├── bitVector.ts │ │ ├── booleanFlatVector.ts │ │ ├── doubleFlatVector.ts │ │ ├── floatFlatVector.spec.ts │ │ ├── floatFlatVector.ts │ │ ├── int32FlatVector.spec.ts │ │ ├── int32FlatVector.ts │ │ ├── int64FlatVector.spec.ts │ │ ├── int64FlatVector.ts │ │ └── stringFlatVector.ts │ ├── fsst-dictionary/ │ │ ├── stringFsstDictionaryVector.spec.ts │ │ └── stringFsstDictionaryVector.ts │ ├── geometry/ │ │ ├── constGeometryVector.ts │ │ ├── constGpuVector.ts │ │ ├── flatGeometryVector.ts │ │ ├── flatGpuVector.ts │ │ ├── geometryType.ts │ │ ├── geometryVector.ts │ │ ├── geometryVectorConverter.spec.ts │ │ ├── geometryVectorConverter.ts │ │ ├── gpuVector.ts │ │ ├── topologyVector.ts │ │ ├── vertexBufferType.ts │ │ ├── zOrderCurve.spec.ts │ │ └── zOrderCurve.ts │ ├── idVector.ts │ ├── sequence/ │ │ ├── int32SequenceVector.ts │ │ ├── int64SequenceVector.spec.ts │ │ ├── int64SequenceVector.ts │ │ └── sequenceVector.ts │ ├── variableSizeVector.ts │ ├── vector.ts │ └── vectorType.ts ├── tsconfig.json └── tsconfig.lint.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ --- BasedOnStyle: Google AccessModifierOffset: -4 AllowShortEnumsOnASingleLine: false AllowShortFunctionsOnASingleLine: Inline AllowShortLambdasOnASingleLine: Inline BinPackArguments: false BinPackParameters: false ColumnLimit: 120 IncludeBlocks: Preserve IndentWidth: 4 PackConstructorInitializers: Never PenaltyBreakAssignment: 80 SortIncludes: false SpacesBeforeTrailingComments: 1 Standard: c++20 --- Language: Cpp --- Language: ObjC BasedOnStyle: Google ObjCSpaceAfterProperty: true ... ================================================ FILE: .cursor/rules/karpathy-guidelines.mdc ================================================ --- description: alwaysApply: true --- # Karpathy behavioral guidelines Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed. **Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment. ## 1. Think Before Coding **Don't assume. Don't hide confusion. Surface tradeoffs.** Before implementing: - State your assumptions explicitly. If uncertain, ask. - If multiple interpretations exist, present them - don't pick silently. - If a simpler approach exists, say so. Push back when warranted. - If something is unclear, stop. Name what's confusing. Ask. ## 2. Simplicity First **Minimum code that solves the problem. Nothing speculative.** - No features beyond what was asked. - No abstractions for single-use code. - No "flexibility" or "configurability" that wasn't requested. - No error handling for impossible scenarios. - If you write 200 lines and it could be 50, rewrite it. Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. ## 3. Surgical Changes **Touch only what you must. Clean up only your own mess.** When editing existing code: - Don't "improve" adjacent code, comments, or formatting. - Don't refactor things that aren't broken. - Match existing style, even if you'd do it differently. - If you notice unrelated dead code, mention it - don't delete it. When your changes create orphans: - Remove imports/variables/functions that YOUR changes made unused. - Don't remove pre-existing dead code unless asked. The test: Every changed line should trace directly to the user's request. ## 4. Goal-Driven Execution **Define success criteria. Loop until verified.** Transform tasks into verifiable goals: - "Add validation" → "Write tests for invalid inputs, then make them pass" - "Fix the bug" → "Write a test that reproduces it, then make it pass" - "Refactor X" → "Ensure tests pass before and after" For multi-step tasks, state a brief plan: ``` 1. [Step] → verify: [check] 2. [Step] → verify: [check] 3. [Step] → verify: [check] ``` Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification. --- **These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes. ================================================ FILE: .gitattributes ================================================ * text=auto eol=lf ================================================ FILE: .github/FUNDING.yml ================================================ github: [maplibre] open_collective: maplibre ================================================ FILE: .github/actions/mlt-setup-java/action.yml ================================================ name: mlt-setup-java description: Setup Java requirements inputs: {} runs: using: "composite" steps: - name: Setup Java uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: '21' - name: Setup Java Gradle uses: gradle/actions/setup-gradle@v5 ================================================ FILE: .github/actions/mlt-setup-node/action.yml ================================================ name: mlt-setup-node description: Setup Node-JS requirements inputs: {} runs: using: "composite" steps: - name: Setup Node.js uses: actions/setup-node@v5 with: node-version-file: 'ts/.nvmrc' cache: 'npm' cache-dependency-path: 'ts/package-lock.json' registry-url: 'https://registry.npmjs.org' ================================================ FILE: .github/actions/mlt-setup-rust/action.yml ================================================ name: mlt-setup-rust description: Setup Rust requirements inputs: toolchain: description: 'Rust toolchain to install: stable (default), nightly, or msrv' required: false default: 'stable' runs: using: "composite" steps: - uses: taiki-e/install-action@v2 with: { tool: 'just,cargo-binstall,cargo-hack,fd-find,cargo-insta,cargo-sort,git-delta' } # Install the requested toolchain (stable, nightly, or msrv) - if: ${{ inputs.toolchain == 'stable' }} name: Install Rust stable uses: dtolnay/rust-toolchain@stable - if: ${{ inputs.toolchain == 'nightly' }} name: Install Rust nightly uses: dtolnay/rust-toolchain@nightly - if: ${{ inputs.toolchain == 'msrv' }} name: Read MSRV id: msrv shell: bash run: echo "value=$(just rust::get-msrv)" >> $GITHUB_OUTPUT - if: ${{ inputs.toolchain == 'msrv' }} name: Install Rust MSRV ${{ steps.msrv.outputs.value }} uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ steps.msrv.outputs.value }} - name: Cache Cargo registry and build artifacts if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' uses: Swatinem/rust-cache@v2 with: { workspaces: rust } ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates # # Config file spec: # https://docs.github.com/en/enterprise-cloud@latest/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem version: 2 updates: # Maintain dependencies for GitHub Actions - package-ecosystem: github-actions directory: "/" schedule: interval: weekly groups: all-actions-version-updates: applies-to: version-updates patterns: - "*" all-actions-security-updates: applies-to: security-updates patterns: - "*" - package-ecosystem: gradle # should this be "maven" or "gradle"? directory: "/java" schedule: interval: daily time: "02:00" groups: all-gradle-version-updates: applies-to: version-updates patterns: - "*" all-gradle-security-updates: applies-to: security-updates patterns: - "*" - package-ecosystem: "npm" directory: "/ts" schedule: interval: daily versioning-strategy: increase groups: all-npm-version-updates: applies-to: version-updates patterns: - "*" all-npm-security-updates: applies-to: security-updates patterns: - "*" # Update Rust dependencies - package-ecosystem: cargo directory: "/rust" schedule: interval: daily time: "02:00" open-pull-requests-limit: 10 groups: all-cargo-version-updates: applies-to: version-updates patterns: - "*" all-cargo-security-updates: applies-to: security-updates patterns: - "*" ================================================ FILE: .github/workflows/autofix.yml ================================================ name: autofix.ci # See https://autofix.ci/security why this name on: pull_request: types: [labeled, opened, synchronize, reopened] workflow_dispatch: permissions: {} jobs: rust-fmt-toml: name: Format Cargo.toml runs-on: ubuntu-latest permissions: contents: read if: | github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened' steps: - name: Checkout sources uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: { persist-credentials: false } - uses: taiki-e/install-action@763e3324d4fd026c9bd284c504378585777a87d5 # v2.62.57 with: { tool: 'just,cargo-sort' } - name: Format Cargo.toml run: just rust::fmt-toml - uses: autofix-ci/action@c5b2d67aa2274e7b5a18224e8171550871fc7e4a # v1.3.4 with: { commit-message: "chore: sort Cargo.toml" } rust-sync-versions: name: Sync Rust-provided bindings version indicators runs-on: ubuntu-latest permissions: contents: read if: | github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened' steps: - name: Checkout sources uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: { persist-credentials: false } - name: Setup Rust run: rustup update stable && rustup default stable - uses: taiki-e/install-action@763e3324d4fd026c9bd284c504378585777a87d5 # v2.62.57 with: { tool: 'just' } - run: sudo apt update && sudo apt install -y jq - name: Sync package.json version from Cargo.toml working-directory: rust run: just rust::sync-wasm-version - name: Sync pyproject.toml version from Cargo.toml working-directory: rust run: just rust::sync-pyo3-version - uses: autofix-ci/action@c5b2d67aa2274e7b5a18224e8171550871fc7e4a # v1.3.4 with: {commit-message: "chore: sync secondary version indicators"} rust-gen-pyo3-stubs: name: Regenerate maplibre_tiles.pyi stub runs-on: ubuntu-latest permissions: contents: read if: | github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: { persist-credentials: false } - uses: taiki-e/install-action@v2 with: { tool: 'just' } - uses: ./.github/actions/mlt-setup-rust - name: Regenerate stub run: just rust::sync-pyo3-stubs # run pre-commit as a formatting tiebreaker - name: Install uv uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 - run: uvx pre-commit run --all-files continue-on-error: true - uses: autofix-ci/action@c5b2d67aa2274e7b5a18224e8171550871fc7e4a with: {commit-message: "chore: regenerate maplibre_tiles.pyi stub"} bless: name: Bless test output runs-on: ubuntu-latest permissions: contents: read if: | github.event.action == 'labeled' && github.event.label.name == 'bless' steps: - name: Checkout sources uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: { persist-credentials: false } - uses: BRAINSia/free-disk-space@7048ffbf50819342ac964ef3998a51c2564a8a75 # v2.1.3 with: tool-cache: false mandb: false android: true dotnet: true haskell: true large-packages: false docker-images: false swap-storage: true - name: Setup Rust run: rustup update stable && rustup default stable - name: Install just uses: taiki-e/install-action@cc33365ec7e3350bc47bf935f247582cc6f68344 # v2.65.12 with: tool: just,cargo-binstall - name: Setup Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 - run: just bless continue-on-error: true # run pre-commit as a formatting tiebreaker - name: Install uv uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 - run: uvx pre-commit run --all-files continue-on-error: true - uses: autofix-ci/action@c5b2d67aa2274e7b5a18224e8171550871fc7e4a # v1.3.4 with: {commit-message: "chore: update blessed test snapshots across all components"} ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: pull_request: push: branches: [main] workflow_dispatch: jobs: changes: runs-on: ubuntu-latest outputs: rust: ${{ steps.filter.outputs.rust }} java: ${{ steps.filter.outputs.java }} ts: ${{ steps.filter.outputs.ts }} cpp: ${{ steps.filter.outputs.cpp }} steps: - uses: actions/checkout@v6.0.2 - uses: dorny/paths-filter@v4 id: filter with: filters: | rust: - 'justfile' - 'rust/mod.just' - '.github/**' - 'rust/**' - 'test/**' java: - 'justfile' - 'java/mod.just' - '.github/**' - 'java/**' - 'test/**' ts: - 'justfile' - 'ts/mod.just' - '.github/**' - 'ts/**' - 'test/**' cpp: - 'justfile' - 'cpp/mod.just' - '.github/**' - 'cpp/**' - 'test/**' rust: needs: changes if: needs.changes.outputs.rust == 'true' uses: ./.github/workflows/rust.yml with: run-release: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} secrets: inherit permissions: contents: write id-token: write java: needs: changes if: needs.changes.outputs.java == 'true' uses: ./.github/workflows/java.yml permissions: id-token: write contents: read ts: needs: changes if: needs.changes.outputs.ts == 'true' uses: ./.github/workflows/ts.yml permissions: contents: read cpp: needs: changes if: needs.changes.outputs.cpp == 'true' uses: ./.github/workflows/cpp.yml permissions: id-token: write contents: read docs: uses: ./.github/workflows/gh-pages.yml permissions: contents: write ci-passed: needs: [rust, java, ts, cpp, docs] if: always() runs-on: ubuntu-latest steps: - run: echo "${{ toJSON(needs) }}" - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') run: exit 1 ================================================ FILE: .github/workflows/cpp.yml ================================================ name: C++ CI on: workflow_call: workflow_dispatch: jobs: cpp-build: permissions: id-token: write # for OIDC Codecov strategy: matrix: os: [macos-15, ubuntu-24.04] runs-on: ${{ matrix.os }} steps: - name: Checkout repository uses: actions/checkout@v6.0.2 with: submodules: recursive fetch-depth: 1 - uses: taiki-e/install-action@v2 with: { tool: 'just,cargo-binstall' } - name: Setup Node.js uses: actions/setup-node@v6 with: node-version-file: 'cpp/.nvmrc' - name: install gcovr (macOS) if: runner.os == 'macOS' run: brew install gcovr - name: install gcovr (Linux) if: runner.os == 'Linux' run: pip3 install gcovr - name: Build, test and generate coverage report run: just -v cpp::coverage - name: Upload coverage to Codecov if: github.repository_owner == 'maplibre' uses: codecov/codecov-action@v6 with: use_oidc: true fail_ci_if_error: true disable_file_fixes: true disable_search: true files: cpp/build/coverage.xml - name: Sanity test for mlt-cpp-json tool run: just -v cpp::test-json - name: Build with Bazel run: just -v cpp::bazel-build - run: just -v assert-git-is-clean ================================================ FILE: .github/workflows/dependabot.yml ================================================ name: Dependabot auto-merge on: pull_request permissions: write-all jobs: dependabot: runs-on: ubuntu-latest if: github.actor == 'dependabot[bot]' steps: - name: Dependabot metadata id: metadata uses: dependabot/fetch-metadata@v3 with: github-token: ${{ secrets.GITHUB_TOKEN }} - name: Approve Dependabot PRs if: steps.metadata.outputs.update-type == 'version-update:semver-patch' run: gh pr review --approve "$PR_URL" env: PR_URL: ${{ github.event.pull_request.html_url }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Enable auto-merge for Dependabot PRs if: steps.metadata.outputs.update-type == 'version-update:semver-patch' run: gh pr merge --auto --squash "$PR_URL" env: PR_URL: ${{ github.event.pull_request.html_url }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/gh-pages.yml ================================================ name: Publish docs on: workflow_call: workflow_dispatch: jobs: gh-pages: runs-on: ubuntu-latest steps: - name: Checkout 🛎️ uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4 - uses: taiki-e/install-action@v2 with: { tool: 'just,cargo-binstall' } - name: Build MLT documentation run: just -v docs-build - name: Deploy 🚀 if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && github.repository_owner == 'maplibre' uses: JamesIves/github-pages-deploy-action@d92aa235d04922e8f08b40ce78cc5442fcfbfa2f # v4.8.0 with: { branch: gh-pages, folder: site } - run: just -v assert-git-is-clean ================================================ FILE: .github/workflows/hotpath-comment.yml ================================================ name: Rust Hotpath Comment on: workflow_run: workflows: ["Rust Hotpath Profile"] types: - completed permissions: contents: read pull-requests: write jobs: comment: runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - uses: actions/checkout@v6.0.2 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - uses: actions/download-artifact@v8 with: name: profile-metrics path: /tmp/metrics/ github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id }} - name: Install hotpath-utils CLI run: cargo install hotpath --bin hotpath-utils --features=utils - name: Post PR comment env: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail GITHUB_BASE_REF=$(cat /tmp/metrics/base_ref.txt) export GITHUB_BASE_REF GITHUB_HEAD_REF=$(cat /tmp/metrics/head_ref.txt) export GITHUB_HEAD_REF hotpath-utils profile-pr \ --head-metrics /tmp/metrics/head_timing.json \ --base-metrics /tmp/metrics/base_timing.json \ --github-token "$GH_TOKEN" \ --pr-number "$(cat /tmp/metrics/pr_number.txt)" \ --benchmark-id "mlt-convert" ================================================ FILE: .github/workflows/hotpath-profile.yml ================================================ name: Rust Hotpath Profile on: pull_request: paths: - "rust/**" permissions: contents: read jobs: profile: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 with: fetch-depth: 0 - uses: ./.github/actions/mlt-setup-rust - name: Create metrics directory run: mkdir -p /tmp/metrics # ── Download benchmark tiles (cached) ────────────────────────────── # 95 fixture tiles are too few for stable hotpath numbers. # See `just rust::download-benchmark-tiles` for details. - name: Cache benchmark tiles id: cache-tiles uses: actions/cache@v5 with: path: /tmp/benchmark-tiles key: hotpath-tiles-protomaps-z6-v1 - name: Download benchmark tiles if: steps.cache-tiles.outputs.cache-hit != 'true' env: GITHUB_TOKEN: ${{ github.token }} run: just rust::download-benchmark-tiles - name: Head benchmark (timing + alloc) working-directory: rust env: HOTPATH_OUTPUT_FORMAT: json HOTPATH_OUTPUT_PATH: /tmp/metrics/head_timing.json run: | cargo run --release -p mlt --features='__hotpath,__hotpath-alloc' -- \ convert /tmp/benchmark-tiles/ /tmp/mlt-head-out/ - name: Checkout base run: git checkout ${{ github.event.pull_request.base.sha }} - name: Base benchmark (timing + alloc) working-directory: rust env: HOTPATH_OUTPUT_FORMAT: json HOTPATH_OUTPUT_PATH: /tmp/metrics/base_timing.json run: | cargo run --release -p mlt --features='__hotpath,__hotpath-alloc' -- \ convert /tmp/benchmark-tiles/ /tmp/mlt-base-out/ - name: Save PR metadata env: PR_NUMBER: ${{ github.event.pull_request.number }} BASE_REF: ${{ github.base_ref }} HEAD_REF: ${{ github.head_ref }} run: | echo "$PR_NUMBER" > /tmp/metrics/pr_number.txt echo "$BASE_REF" > /tmp/metrics/base_ref.txt echo "$HEAD_REF" > /tmp/metrics/head_ref.txt - uses: actions/upload-artifact@v7 with: name: profile-metrics path: /tmp/metrics/ retention-days: 1 ================================================ FILE: .github/workflows/java-release.yml ================================================ name: Java - Release on: push: tags: - 'java-v*.*.*' workflow_dispatch: jobs: release: runs-on: ubuntu-latest permissions: contents: write steps: - name: Checkout code uses: actions/checkout@v6.0.2 - name: Set up JDK uses: actions/setup-java@v5 with: java-version: '21' distribution: 'temurin' - uses: taiki-e/install-action@v2 with: { tool: 'just,cargo-binstall' } - name: Get version from tag id: get_version run: | version="$(just ci-extract-version java ${{ github.ref_name }})" echo version="$version" >> "$GITHUB_ENV" - name: Make gradlew executable run: chmod +x ./java/gradlew - name: Publish to Maven Central working-directory: java run: ./gradlew publishToMavenCentral -Pversion=${{ env.version }} env: ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }} ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }} - name: Build CLI tool working-directory: java run: ./gradlew cli - run: just -v assert-git-is-clean - name: Rename CLI artifacts run: | cp java/mlt-cli/build/libs/encode.jar mlt-encode.jar cp java/mlt-cli/build/libs/decode.jar mlt-decode.jar - name: Add CLI artifacts to GitHub release uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: tag_name: java-v${{ env.version }} fail_on_unmatched_files: true name: java-v${{ env.version }} files: | mlt-encode.jar mlt-decode.jar ================================================ FILE: .github/workflows/java.yml ================================================ name: Java CI on: workflow_call: workflow_dispatch: defaults: run: shell: bash jobs: test-java: permissions: id-token: write # for OIDC Codecov runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] steps: - uses: actions/checkout@v6.0.2 - uses: taiki-e/install-action@v2 with: { tool: 'just,cargo-binstall' } - uses: ./.github/actions/mlt-setup-java - run: just -v java::env-info - if: matrix.os == 'ubuntu-latest' name: Lint Java code. If fails, run `just java::fmt` to reformat, and `just java::lint` to see issues locally run: just -v java::lint - run: just -v java::test-coverage - run: just -v java::test-cli - name: Test Maven local publishing run: just -v java::test-publish - run: npm ci working-directory: java/encoding-server - run: npm run lint working-directory: java/encoding-server - run: just -v assert-git-is-clean - name: Upload coverage to Codecov if: github.repository_owner == 'maplibre' uses: codecov/codecov-action@v6 with: use_oidc: true directory: java - name: Test synthetic MLT generation run: just -v java::ci-check-synthetic-mlts ================================================ FILE: .github/workflows/python-release.yml ================================================ # This file is autogenerated by maturin v1.12.4 # To update, run the following command, but make sure to keep the adjustments that we did. # maturin generate-ci github name: Python Release on: push: branches: - main tags: - 'python-mlt-v*' pull_request: paths: - 'justfile' - 'rust/mod.just' - '.github/**' - 'rust/**' - 'test/**' workflow_dispatch: permissions: contents: read jobs: linux: runs-on: ${{ matrix.platform.runner }} strategy: fail-fast: false matrix: platform: - runner: ubuntu-22.04 target: x86_64 # FIXME: FSST does not seem to support 32bit targets #- runner: ubuntu-22.04 # target: x86 # FIXME: Python::initialize not avaliable for linking #- runner: ubuntu-22.04 # target: aarch64 # FIXME: error: unknown type name '__m128i' and related #- runner: ubuntu-22.04 # target: armv7 # FIXME: cpp standard mismatch #- runner: ubuntu-22.04 # target: s390x # FIXME: error: unknown type name '__m128i' and related #- runner: ubuntu-22.04 # target: ppc64le steps: - uses: actions/checkout@v6 - uses: ./.github/actions/mlt-setup-rust - uses: actions/setup-python@v6 with: { python-version: '3.x' } - name: Build wheels uses: PyO3/maturin-action@v1 with: target: ${{ matrix.platform.target }} args: --release --zig --out dist --find-interpreter --locked --compatibility pypi --future-incompat-report --manifest-path rust/mlt-py/Cargo.toml sccache: false manylinux: auto - name: Upload wheels uses: actions/upload-artifact@v7 with: name: wheels-linux-${{ matrix.platform.target }} path: dist # FIXME: our --bin bins/stub-gen requires pyo3::Python::initialize, but unsure how to do that on musllinux. We don't need it for the library! #musllinux: # runs-on: ${{ matrix.platform.runner }} # strategy: # fail-fast: false # matrix: # platform: # - runner: ubuntu-22.04 # target: x86_64 # - runner: ubuntu-22.04 # target: x86 # - runner: ubuntu-22.04 # target: aarch64 # - runner: ubuntu-22.04 # # FIXME: FSST does not seem to support 32bit targets # #- runner: ubuntu-22.04 # # target: x86 # # FIXME: Python::initialize not avaliable for linking # #- runner: ubuntu-22.04 # # target: aarch64 # # FIXME: error: unknown type name '__m128i' and related # #- runner: ubuntu-22.04 # # target: armv7 # steps: # - uses: actions/checkout@v6 # - uses: ./.github/actions/mlt-setup-rust # - uses: actions/setup-python@v6 # with: { python-version: '3.x' } # - name: Build wheels # uses: PyO3/maturin-action@v1 # with: # target: ${{ matrix.platform.target }} # args: --release --zig --out dist --find-interpreter --locked --compatibility pypi --future-incompat-report --manifest-path rust/mlt-py/Cargo.toml # sccache: false # manylinux: musllinux_1_2 # - name: Upload wheels # uses: actions/upload-artifact@v7 # with: # name: wheels-musllinux-${{ matrix.platform.target }} # path: dist # FIXME: Failed to run `zig cc -v` with status exit code: 1: error: unable to create compilation: LibCStdLibHeaderNotFound #windows: # runs-on: ${{ matrix.platform.runner }} # strategy: # fail-fast: false # matrix: # platform: # - runner: windows-latest # target: x64 # python_arch: x64 # - runner: windows-latest # target: x86 # python_arch: x86 # - runner: windows-11-arm # target: aarch64 # python_arch: arm64 # steps: # - uses: actions/checkout@v6 # - uses: ./.github/actions/mlt-setup-rust # - uses: actions/setup-python@v6 # with: # python-version: 3.13 # architecture: ${{ matrix.platform.python_arch }} # - name: Build wheels # uses: PyO3/maturin-action@v1 # with: # target: ${{ matrix.platform.target }} # args: --release --zig --out dist --find-interpreter --locked --compatibility pypi --future-incompat-report --manifest-path rust/mlt-py/Cargo.toml # sccache: false # - name: Upload wheels # uses: actions/upload-artifact@v7 # with: # name: wheels-windows-${{ matrix.platform.target }} # path: dist macos: runs-on: ${{ matrix.platform.runner }} strategy: fail-fast: false matrix: platform: - runner: macos-15-intel target: x86_64 - runner: macos-latest target: aarch64 steps: - uses: actions/checkout@v6 - uses: ./.github/actions/mlt-setup-rust - uses: actions/setup-python@v6 with: { python-version: '3.x' } - name: Build wheels uses: PyO3/maturin-action@v1 with: target: ${{ matrix.platform.target }} args: --release --zig --out dist --find-interpreter --locked --compatibility pypi --future-incompat-report --manifest-path rust/mlt-py/Cargo.toml sccache: false - name: Upload wheels uses: actions/upload-artifact@v7 with: name: wheels-macos-${{ matrix.platform.target }} path: dist sdist: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Build sdist uses: PyO3/maturin-action@v1 with: command: sdist args: --out dist --manifest-path rust/mlt-py/Cargo.toml - name: Upload sdist uses: actions/upload-artifact@v7 with: { name: 'wheels-sdist', path: 'dist' } release: name: Release runs-on: ubuntu-latest if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} needs: - linux # FIXME: does seem to require dev-only dependencys #- musllinux # FIXME: Failed to run `zig cc -v` with status exit code: 1: error: unable to create compilation: LibCStdLibHeaderNotFound #- windows - macos - sdist permissions: # Use to sign the release artifacts id-token: write # Used to upload release artifacts contents: write # Used to generate artifact attestation attestations: write steps: - uses: actions/download-artifact@v8 - name: Generate artifact attestation uses: actions/attest-build-provenance@v4 with: { subject-path: 'wheels-*/*' } - name: Install uv if: ${{ startsWith(github.ref, 'refs/tags/') }} uses: astral-sh/setup-uv@v7 - name: Publish to PyPI if: ${{ startsWith(github.ref, 'refs/tags/') }} run: uv publish 'wheels-*/*' ================================================ FILE: .github/workflows/rust.yml ================================================ name: Rust CI on: workflow_call: inputs: run-release: type: boolean default: false workflow_dispatch: jobs: lint: name: Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: ./.github/actions/mlt-setup-rust - run: just -v rust::ci-check - run: just -v rust::ci-lint coverage: name: Coverage permissions: id-token: write # for OIDC Codecov runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: ./.github/actions/mlt-setup-rust with: { toolchain: nightly } # because of covs --doctests - uses: taiki-e/install-action@v2 with: { tool: "cargo-llvm-cov" } - run: just -v rust::ci-coverage - name: Upload coverage to Codecov if: github.repository_owner == 'maplibre' uses: codecov/codecov-action@v6 with: use_oidc: true fail_ci_if_error: true disable_search: true files: rust/target/llvm-cov/lcov.info - run: just -v assert-git-is-clean test-wasm: name: Test wasm runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: ./.github/actions/mlt-setup-rust - run: rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu - name: Setup Node.js uses: actions/setup-node@v6 with: node-version-file: "rust/mlt-wasm/.nvmrc" cache: "npm" cache-dependency-path: "rust/mlt-wasm/package-lock.json" - run: just -v rust::ci-test-wasm fuzz: name: Fuzz ${{ matrix.target }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - target: layer - target: decoded_layer steps: - uses: actions/checkout@v6.0.2 - uses: ./.github/actions/mlt-setup-rust with: { toolchain: nightly } - run: just -v rust::ci-fuzz-build ${{ matrix.target }} - if: matrix.target == 'layer' run: just -v rust::fuzz-seed ${{ matrix.target }} - run: just -v rust::ci-fuzz-run ${{ matrix.target }} - name: Generate fuzz coverage if: github.repository_owner == 'maplibre' run: just -v rust::ci-fuzz-cov ${{ matrix.target }} - name: Upload fuzz coverage artifact if: github.repository_owner == 'maplibre' uses: actions/upload-artifact@v7 with: name: fuzz-coverage-${{ matrix.target }} path: rust/target/llvm-cov/fuzz-${{ matrix.target }}.lcov # MSRV Testing is not really needed at this stage of code completeness # test-msrv: # name: Test MSRV # runs-on: ubuntu-latest # steps: # - uses: actions/checkout@v6.0.2 # - uses: ./.github/actions/mlt-setup-rust # with: { toolchain: msrv } # - run: just -v rust::ci-test-msrv test-publish: name: Test workspace publishing using --dry-run runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: ./.github/actions/mlt-setup-rust - name: Test workspace publishing run: just -v rust::test-publish - run: just -v assert-git-is-clean build-release-binaries: name: Build release binaries for ${{ matrix.target }} strategy: fail-fast: false matrix: include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu artifact_name: mlt-x86_64-unknown-linux-gnu.tar.gz - os: ubuntu-24.04-arm target: aarch64-unknown-linux-gnu artifact_name: mlt-aarch64-unknown-linux-gnu.tar.gz - os: macos-latest target: aarch64-apple-darwin artifact_name: mlt-aarch64-apple-darwin.tar.gz - os: windows-latest target: x86_64-pc-windows-msvc artifact_name: mlt-x86_64-pc-windows-msvc.zip runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6.0.2 - uses: ./.github/actions/mlt-setup-rust - run: just -v rust::ci-build-release ${{ matrix.target }} ${{ matrix.artifact_name }} - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: ${{ matrix.artifact_name }} path: ${{ matrix.artifact_name }} build-wasm-npm: name: Build @maplibre/mlt-wasm npm package runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: ./.github/actions/mlt-setup-rust with: { toolchain: nightly } - run: rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu - name: Setup Node.js uses: actions/setup-node@v6 with: node-version-file: "rust/mlt-wasm/.nvmrc" cache: "npm" cache-dependency-path: "rust/mlt-wasm/package-lock.json" - name: Build run: just -v rust::wasm-build - name: Merge license files into LICENSE.txt working-directory: rust/mlt-wasm run: | { echo "===== MIT License =====" cat ../../LICENSE-MIT echo echo "===== Apache License 2.0 =====" cat ../../LICENSE-APACHE } > LICENSE.txt - name: Pack working-directory: rust/mlt-wasm run: npm pack - name: Upload npm package artifact uses: actions/upload-artifact@v7 with: name: mlt-wasm-npm path: rust/mlt-wasm/maplibre-mlt-wasm-*.tgz # This job checks if any of the previous jobs failed or were canceled. # This approach also allows some jobs to be skipped if they are not needed. ci-passed-rust: needs: - lint - coverage # re-enable once we have stability guarantees regarding MSRV # - test-msrv - test-wasm - build-release-binaries - test-publish - build-wasm-npm if: always() runs-on: ubuntu-latest steps: - name: Result of the needed steps run: echo "${{ toJSON(needs) }}" - if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} run: exit 1 # Release unpublished packages or create a PR with changes release-plz: needs: [ci-passed-rust] if: | always() && needs.ci-passed-rust.result == 'success' && inputs.run-release && github.repository_owner == 'maplibre' runs-on: ubuntu-latest permissions: contents: read id-token: write concurrency: group: release-plz-${{ github.ref }} cancel-in-progress: false steps: - run: rustup update stable && rustup default stable - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2 with: { fetch-depth: 0, persist-credentials: false } - uses: ./.github/actions/mlt-setup-rust - name: Publish to crates.io if crate's version is newer uses: release-plz/action@1528104d2ca23787631a1c1f022abb64b34c1e11 # v0.5.128 id: release with: { command: release, manifest_path: ./rust/Cargo.toml } env: GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }} - if: ${{ steps.release.outputs.releases_created == 'false' }} name: If version is the same, create a PR proposing new version and changelog for the next release uses: release-plz/action@1528104d2ca23787631a1c1f022abb64b34c1e11 # v0.5.128 with: { command: release-pr, manifest_path: ./rust/Cargo.toml } env: GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }} outputs: releases_created: ${{ steps.release.outputs.releases_created }} releases: ${{ steps.release.outputs.releases }} upload-release-binaries: name: Upload release binaries to GitHub releases needs: [release-plz] if: ${{ needs.release-plz.outputs.releases_created == 'true' }} runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v6.0.2 - uses: taiki-e/install-action@v2 with: { tool: "just" } - name: Get mlt version for release tag id: get_version shell: bash run: just -v rust::ci-get-release-info mlt >> $GITHUB_OUTPUT - name: Download all artifacts uses: actions/download-artifact@v8 with: { path: artifacts } - name: Add binaries to GitHub release uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: tag_name: ${{ steps.get_version.outputs.tag }} files: | artifacts/**/*.tar.gz artifacts/**/*.zip fail_on_unmatched_files: true # Publish @maplibre/mlt-wasm to npm after mlt-wasm is released to crates.io. # The npm package tarball was already built and uploaded as an artifact by build-wasm-npm. publish-wasm-npm: name: Publish @maplibre/mlt-wasm to npm needs: [release-plz] if: | needs.release-plz.outputs.releases_created == 'true' && contains(fromJSON(needs.release-plz.outputs.releases || '[]').*.package_name, 'mlt-wasm') runs-on: ubuntu-latest permissions: contents: write id-token: write steps: - name: Get mlt-wasm release version id: version env: RELEASES: ${{ needs.release-plz.outputs.releases }} run: | echo "value=$(echo "$RELEASES" | jq -r '.[] | select(.package_name == "mlt-wasm") | .version')" >> $GITHUB_OUTPUT - name: Set prerelease flag run: | version="${{ steps.version.outputs.value }}" echo version="$version" >> "$GITHUB_ENV" if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo prerelease=false >> "$GITHUB_ENV" else echo prerelease=true >> "$GITHUB_ENV" fi - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: "24" registry-url: "https://registry.npmjs.org" - name: Download npm package artifact uses: actions/download-artifact@v8 with: name: mlt-wasm-npm path: mlt-wasm-pkg - name: Release (GitHub) - Create Draft uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: name: mlt-wasm-js-v${{ env.version }} tag_name: mlt-wasm-js-v${{ env.version }} prerelease: ${{ env.prerelease }} draft: true - name: Publish npm package run: | package_tgz="$(find mlt-wasm-pkg -type f -name 'maplibre-mlt-wasm-*.tgz' -print -quit)" test -n "$package_tgz" || { echo "::error::Package file not found in mlt-wasm-pkg"; exit 1; } npm publish "$package_tgz" --access=public - name: Publish GitHub release (remove draft) uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: tag_name: mlt-wasm-js-v${{ env.version }} name: mlt-wasm-js-v${{ env.version }} draft: false ================================================ FILE: .github/workflows/ts-bump-version.yml ================================================ name: Manual TypeScript Version Bump on: workflow_dispatch: inputs: release_type: type: choice description: 'Type of version bump' options: - major - minor - patch - premajor - preminor - prepatch - prerelease jobs: js-bump-version: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: taiki-e/install-action@v2 with: { tool: 'just,cargo-binstall' } - name: Update version, commit, and create tag working-directory: ts run: | npm version ${{ github.event.inputs.release_type }} --no-git-tag-version echo version="$(node -p "require('./package.json').version")" >> "$GITHUB_ENV" - name: Create Pull Request uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 with: commit-message: bump @maplibre/mlt version to ${{ env.version }} branch: mlt-js-version-${{ env.version }} title: bump @maplibre/mlt version to ${{ env.version }} - run: just -v assert-git-is-clean ================================================ FILE: .github/workflows/ts-release.yml ================================================ name: TypeScript - Release permissions: id-token: write # Required for trusted publishing contents: write on: push: branches: - main workflow_dispatch: jobs: release-check: name: Check if version changed runs-on: ubuntu-latest defaults: run: shell: bash steps: - uses: actions/checkout@v6.0.2 - uses: taiki-e/install-action@v2 with: { tool: 'just,cargo-binstall' } - name: Check if version has been updated id: check uses: EndBug/version-check@095362f3cd50f690c8fa0e6afeea81834bd8d320 with: file-name: ts/package.json - run: just -v assert-git-is-clean outputs: publish: ${{ steps.check.outputs.changed }} release: name: Release needs: release-check if: ${{ needs.release-check.outputs.publish == 'true' }} runs-on: ubuntu-latest defaults: run: working-directory: ts steps: - uses: actions/checkout@v6.0.2 - uses: taiki-e/install-action@v2 with: { tool: 'just,cargo-binstall' } - uses: ./.github/actions/mlt-setup-node - name: Get version run: | version="$(node -p "require('./package.json').version")" echo version="$version" >> "$GITHUB_ENV" if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo prerelease=false >> "$GITHUB_ENV" else echo prerelease=true >> "$GITHUB_ENV" fi - name: Install run: npm ci - name: Build run: npm run build - name: Merge license files into LICENSE.txt run: | { echo "===== MIT License =====" cat ../LICENSE-MIT echo echo "===== Apache License 2.0 =====" cat ../LICENSE-APACHE } > LICENSE.txt - name: Release (GitHub) - Create Draft uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: name: js-v${{ env.version }} tag_name: js-v${{ env.version }} prerelease: ${{ env.prerelease }} draft: true - if: github.repository_owner == 'maplibre' name: Publish npm package run: npm publish --access=public - name: Publish GitHub release (remove draft) uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: tag_name: js-v${{ env.version }} name: js-v${{ env.version }} draft: false - run: just -v assert-git-is-clean ================================================ FILE: .github/workflows/ts.yml ================================================ name: TypeScript CI on: workflow_call: workflow_dispatch: defaults: run: shell: bash jobs: test-js: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: taiki-e/install-action@v2 with: { tool: 'just,cargo-binstall' } - uses: ./.github/actions/mlt-setup-node - name: Lint JS code. If fails, run `just ts::fmt` to reformat, and `just ts::lint` to see issues locally run: just -v ts::lint - run: just -v ts::test - run: just -v ts::build - name: Codecov upload if: "github.repository_owner == 'maplibre' && !cancelled()" uses: codecov/codecov-action@v6 with: files: ts/coverage/coverage-final.json - run: just -v assert-git-is-clean ================================================ FILE: .gitignore ================================================ *.class *.diff.geojson *.gcov *.log *.new.geojson *.actual.json .DS_Store .cache/ .idea/ .kotlin .venv .vscode/ MODULE.bazel.lock bazel-* bin/ codecov* coverage/ dist/ site/ tmp/ node_modules ================================================ FILE: .gitmodules ================================================ [submodule "cpp/vendor/googletest"] path = cpp/vendor/googletest url = https://github.com/google/googletest.git [submodule "cpp/vendor/simde"] path = cpp/vendor/simde url = https://github.com/simd-everywhere/simde.git [submodule "cpp/vendor/fastpfor"] path = cpp/vendor/fastpfor url = https://github.com/fast-pack/FastPFor.git [submodule "cpp/vendor/json"] path = cpp/vendor/json url = https://github.com/nlohmann/json.git ================================================ FILE: .pre-commit-config.yaml ================================================ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks # exclusions should be separated with a pipe (|) character and a newline exclude: | (?x)^( test/expected/.* |java/gradlew(\.bat)? |\.cursor/rules/.* )$ ci: autoupdate_schedule: monthly # sometimes fails https://github.com/keith/pre-commit-buildifier/issues/13 skip: [ buildifier ] repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: check-added-large-files args: ['--maxkb=553'] - id: check-executables-have-shebangs - id: check-json exclude: '.+/tsconfig.json' - id: check-shebang-scripts-are-executable exclude: '.+\.rs' # would be triggered by #![some_attribute] - id: check-symlinks - id: check-toml - id: check-yaml args: [ --allow-multiple-documents ] exclude: mkdocs.yml - id: destroyed-symlinks - id: end-of-file-fixer - id: mixed-line-ending args: [ --fix=lf ] - id: trailing-whitespace - repo: https://github.com/pre-commit/mirrors-clang-format rev: v22.1.2 hooks: - id: clang-format types: [ c++ ] args: ["-style=file:.clang-format"] - repo: https://github.com/Mateusz-Grzelinski/actionlint-py rev: v1.7.12.24 hooks: - id: actionlint additional_dependencies: [ shellcheck-py ] exclude: '\.github/workflows/rust(-legacy)?\.yml' - repo: local hooks: - id: cargo-fmt name: Rust Format description: "Automatically format Rust code with cargo fmt" entry: sh -c "cd rust && cargo fmt --all" language: rust pass_filenames: false - repo: https://github.com/keith/pre-commit-buildifier rev: 8.5.1.1 hooks: - id: buildifier - repo: https://github.com/biomejs/pre-commit rev: v2.4.10 hooks: - id: biome-check args: [--unsafe] exclude: '.*\.json$' ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of conduct [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](https://github.com/maplibre/maplibre/blob/main/CODE_OF_CONDUCT.md) ================================================ FILE: LICENSE-APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2024 MapLibre contributors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: LICENSE-CC0 ================================================ Creative Commons Legal Code CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. ================================================ FILE: LICENSE-MIT ================================================ MIT License Copyright (c) 2024 MapLibre contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: MODULE.bazel ================================================ module(name = "maplibre-tile-spec") bazel_dep(name = "platforms", version = "1.0.0") bazel_dep(name = "rules_cc", version = "0.2.8") ================================================ FILE: README.md ================================================

MapLibre Logo

# MapLibre Tile (MLT) > [!NOTE] > [The specification](https://maplibre.org/maplibre-tile-spec/specification/) is deemed stable as of October 2025. However, as a living standard, experimental features may continue to evolve. Implementations and integrations are being developed, refer to the [implementation status](https://maplibre.org/maplibre-tile-spec/implementation-status/) page for the current status. The MapLibre Tile specification is mainly inspired by the [Mapbox Vector Tile (MVT)](https://github.com/mapbox/vector-tile-spec) specification, but has been redesigned from the ground up to address the challenges of rapidly growing geospatial data volumes and complex next-generation geospatial source formats as well as to leverage the capabilities of modern hardware and APIs. MLT is specifically designed for modern and next generation graphics APIs to enable high-performance processing and rendering of large (planet-scale) 2D and 2.5 basemaps. In particular, MLT offers the following features: - **Improved compression ratio**: up to 6x on large encoded tiles, based on a column oriented layout with recursively applied (custom) lightweight encodings. This leads to reduced latency, storage, and egress costs and, in particular, improved cache utilization - **Better decoding performance**: fast lightweight encodings which can be used in combination with SIMD/vectorization instructions - **Support for linear referencing and m-values** to efficiently support the upcoming next generation source formats such as Overture Maps (GeoParquet) - **Support 3D coordinates**, i.e. elevation - **Support complex types**, including nested properties, lists and maps - **Improved processing performance**, based on storage and in-memory formats that are specifically designed for modern GL APIs, allowing for efficient processing on both CPU and GPU. The formats are designed to be loaded into GPU buffers with little or no additional processing 📝 For a more in-depth exploration of MLT have a look at the [following slides](https://github.com/mactrem/presentations/blob/main/FOSS4G_2024_Europe/FOSS4G_2024_Europe.pdf), watch [this talk](https://www.youtube.com/watch?v=YHcoAFcsES0) or read [this paper](https://dl.acm.org/doi/10.1145/3748636.3763208) by MLT inventor Markus Tremmel. ## Directory Structure - `/spec` MLT specification and related documentation - `/test` Test MVT tiles and the expected MLT conversion results - `/java` Java encoder for converting MVT to MLT, as well as a decoder parsing MLT to an in-memory representation. - `/js` Javascript decoder - `/rust` Rust decoder ## Getting Involved Join the `#maplibre-tile-format` Slack channel at OSM US: get an invite at https://slack.openstreetmap.us/ ### Development * This project is easier to develop with [just](https://just.systems/man/en/), a modern alternative to `make`. * To get a list of available commands, run `just`. * To run tests, use `just test`. ## License * All project documentation and specification content is licensed under [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/) - Public Domain Dedication. * All code is dual licensed under [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) and [MIT license](http://opensource.org/licenses/MIT), at your option. * Tile test data in the `/test` directory has different licenses depending on the source of that data. ### Contribution Unless you explicitly state otherwise, any code contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. Similarly, any documentation or specification contributions shall be licensed under CC0 1.0 Universal. ## Citing If you use MapLibre Tile in your research, please cite our paper: ```bibtex @inproceedings{tremmel2025maplibretile, title = {MapLibre Tile: A Next Generation Vector Tile Format}, author = {Tremmel, Markus and Zink, Roland}, booktitle = {Proceedings of the 33rd ACM International Conference on Advances in Geographic Information Systems}, series = {SIGSPATIAL '25}, year = {2025}, pages = {1118--1121}, doi = {10.1145/3748636.3763208}, url = {https://doi.org/10.1145/3748636.3763208} } ``` ================================================ FILE: SECURITY_POLICY.txt ================================================ For an up-to-date policy refer to https://github.com/maplibre/maplibre/blob/main/SECURITY_POLICY.txt ================================================ FILE: biome.json ================================================ { "$schema": "https://biomejs.dev/schemas/2.0.5/schema.json", "css": { "formatter": { "indentWidth": 2, "indentStyle": "space" } }, "javascript": { "formatter": { "indentWidth": 2, "indentStyle": "space" } } } ================================================ FILE: cpp/.clang-tidy ================================================ --- Checks: [ android-*, boost-*, bugprone-*, clang-analyzer-core*, clang-analyzer-cplusplus*, clang-analyzer-deadcode*, clang-analyzer-optin.cplusplus*, clang-analyzer-optin.performance.Padding, clang-analyzer-security*, clang-diagnostic-*, cppcoreguidelines-avoid-goto, cppcoreguidelines-no-malloc, google-*, llvm-*, misc-*, modernize-*, performance-*, portability-*, readability-*, -bugprone-branch-clone, -bugprone-easily-swappable-parameters, -bugprone-exception-escape, -bugprone-forward-declaration-namespace, -bugprone-forwarding-reference-overload, -bugprone-implicit-widening-of-multiplication-result, -bugprone-macro-parentheses, -bugprone-narrowing-conversions, -bugprone-reserved-identifier, -bugprone-signed-char-misuse, -bugprone-sizeof-expression, -bugprone-unchecked-optional-access, -bugprone-unhandled-exception-at-new, -bugprone-unhandled-self-assignment, -clang-analyzer-optin.cplusplus.UninitializedObject, -clang-analyzer-core.NonNullParamChecker, -clang-analyzer-core.NullDereference, -clang-analyzer-core.uninitialized.Assign, -clang-analyzer-core.UndefinedBinaryOperatorResult, -clang-analyzer-security.FloatLoopCounter, -google-build-explicit-make-pair, -google-build-namespaces, -google-build-using-namespace, -google-default-arguments, -google-explicit-constructor, -google-objc-global-variable-declaration, -google-readability-braces-around-statements, -google-readability-casting, -google-readability-function-size, -google-readability-function, -google-readability-namespace-comments, -google-readability-todo, -google-runtime-int, -google-runtime-references, -llvm-else-after-return, -llvm-header-guard, -llvm-include-order, -llvm-namespace-comment, -llvm-qualified-auto, -llvm-twine-local, -misc-confusable-identifiers, -misc-const-correctness, -misc-no-recursion, -misc-non-private-member-variables-in-classes, -misc-static-assert, -modernize-avoid-bind, -modernize-avoid-c-arrays, -modernize-concat-nested-namespaces, -modernize-macro-to-enum, -modernize-pass-by-value, -modernize-return-braced-init-list, -modernize-use-auto, -modernize-use-default-member-init, -modernize-use-emplace, -modernize-use-equals-default, -modernize-use-nodiscard, -modernize-use-nullptr, -modernize-use-trailing-return-type, -performance-move-const-arg, -performance-noexcept-move-constructor, -performance-no-int-to-ptr, -performance-unnecessary-value-param, -readability-avoid-const-params-in-decls, -readability-braces-around-statements, -readability-const-return-type, -readability-container-size-empty, -readability-convert-member-functions-to-static, -readability-else-after-return, -readability-function-cognitive-complexity, -readability-function-size, -readability-identifier-length, -readability-identifier-naming, -readability-implicit-bool-conversion, -readability-inconsistent-declaration-parameter-name, -readability-isolate-declaration, -readability-magic-numbers, -readability-make-member-function-const, -readability-named-parameter, -readability-non-const-parameter, -readability-qualified-auto, -readability-redundant-access-specifiers, -readability-redundant-declaration, -readability-redundant-member-init, -readability-redundant-string-init, -readability-simplify-boolean-expr, -readability-static-accessed-through-instance -readability-static-definition-in-anonymous-namespace, -readability-suspicious-call-argument, -readability-uppercase-literal-suffix, -readability-use-anyofallof, -modernize-loop-convert, # since C++20 this complains about reverse loops with iterators, but good ranges support only landed in clang 15 -performance-enum-size, -misc-include-cleaner, -readability-redundant-inline-specifier, -readability-avoid-nested-conditional-operator, -cppcoreguidelines-pro-type-static-cast-downcast, # no RTTI ] WarningsAsErrors: '*' HeaderFilterRegex: '.*' CheckOptions: - key: performance-unnecessary-value-param.AllowedTypes value: 'exception_ptr' ================================================ FILE: cpp/.gitignore ================================================ /build /.cache compile_commands.json cmake_test_discovery* ================================================ FILE: cpp/.nvmrc ================================================ 24.13 ================================================ FILE: cpp/BUILD.bazel ================================================ load("@rules_cc//cc:cc_library.bzl", "cc_library") cc_library( name = "mlt_internal_headers", hdrs = glob(["src/mlt/**/*.hpp"]), include_prefix = "", strip_include_prefix = "src", visibility = ["//visibility:private"], ) cc_library( name = "mlt_cpp", srcs = glob([ "src/mlt/**/*.cpp", ]), hdrs = glob([ "include/mlt/**/*.hpp", ]), copts = select({ "@platforms//os:windows": [ "/std:c++20", ], "//conditions:default": [ "-std=c++20", "-Wall", "-Wextra", ], }), defines = [ "MLT_WITH_JSON=0", "MLT_WITH_FASTPFOR=0", ], implementation_deps = [":mlt_internal_headers"], include_prefix = "", strip_include_prefix = "include", visibility = ["//visibility:public"], ) ================================================ FILE: cpp/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.25) project(mlt-cpp LANGUAGES CXX) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) include(AddCXXCompilerFlag) # generate compile_commands.json https://cmake.org/cmake/help/latest/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_CXX_STANDARD 20) if(PROJECT_IS_TOP_LEVEL AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") set(CMAKE_CXX_FLAGS_COVERAGE "-g -O0 --coverage") if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") string(APPEND CMAKE_CXX_FLAGS_COVERAGE " -fprofile-abs-path") endif() set(CMAKE_EXE_LINKER_FLAGS_COVERAGE "--coverage") set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE "--coverage") set(CMAKE_MODULE_LINKER_FLAGS_COVERAGE "--coverage") endif() add_library(mlt-cpp STATIC) option(MLT_WITH_JSON "Include JSON support" ON) option(MLT_WITH_FASTPFOR "Include FastPFor support" ON) option(MLT_WITH_FASTPFOR_SIMD "Include SIMD/NEON support in FastPFor decoding" ON) option(MLT_WITH_TESTS "Include Tests" ON) option(MLT_WITH_TOOLS "Include CLI tools" ON) set_target_properties( mlt-cpp PROPERTIES INTERFACE_MAPLIBRE_NAME "MapLibre Tile Format" INTERFACE_MAPLIBRE_URL "https://github.com/maplibre/maplibre-tile-spec" INTERFACE_MAPLIBRE_AUTHOR "MapLibre" INTERFACE_MAPLIBRE_LICENSE "${PROJECT_SOURCE_DIR}/../LICENSE-APACHE" ) add_cxx_compiler_flag(-Wall) add_cxx_compiler_flag(-Werror) add_cxx_compiler_flag(-Wextra) add_cxx_compiler_flag(-Wdeprecated-declarations) add_cxx_compiler_flag(-Winvalid-offsetof) add_cxx_compiler_flag(-Wno-block-capture-autoreleasing) add_cxx_compiler_flag(-Wno-bool-conversion) add_cxx_compiler_flag(-Wno-c++11-extensions) add_cxx_compiler_flag(-Wno-comma) add_cxx_compiler_flag(-Wno-constant-conversion) add_cxx_compiler_flag(-Wno-conversion) add_cxx_compiler_flag(-Wno-empty-body) add_cxx_compiler_flag(-Wno-enum-conversion) add_cxx_compiler_flag(-Wno-exit-time-destructors) add_cxx_compiler_flag(-Wno-float-conversion) add_cxx_compiler_flag(-Wno-four-char-constants) add_cxx_compiler_flag(-Wno-implicit-fallthrough) add_cxx_compiler_flag(-Wno-infinite-recursion) add_cxx_compiler_flag(-Wno-missing-braces) add_cxx_compiler_flag(-Wno-missing-field-initializers) add_cxx_compiler_flag(-Wno-move) add_cxx_compiler_flag(-Wno-newline-eof) add_cxx_compiler_flag(-Wno-non-literal-null-conversion) add_cxx_compiler_flag(-Wno-non-virtual-dtor) add_cxx_compiler_flag(-Wno-objc-literal-conversion) add_cxx_compiler_flag(-Wno-overloaded-virtual) add_cxx_compiler_flag(-Wno-range-loop-analysis) add_cxx_compiler_flag(-Wno-return-type) add_cxx_compiler_flag(-Wno-semicolon-before-method-body) add_cxx_compiler_flag(-Wno-shadow) add_cxx_compiler_flag(-Wno-sign-conversion) add_cxx_compiler_flag(-Wno-trigraphs) add_cxx_compiler_flag(-Wno-uninitialized) add_cxx_compiler_flag(-Wno-unknown-pragmas) add_cxx_compiler_flag(-Wno-unused-function) add_cxx_compiler_flag(-Wno-unused-label) add_cxx_compiler_flag(-Wno-unused-parameter) add_cxx_compiler_flag(-Wno-unused-variable) add_cxx_compiler_flag(-Wparentheses) add_cxx_compiler_flag(-Wshorten-64-to-32) add_cxx_compiler_flag(-Wswitch) add_cxx_compiler_flag(-Wunused-value) add_cxx_compiler_flag(-fstrict-aliasing) add_cxx_compiler_flag(-wd4061) # MSVC: enum member not handled in switch add_cxx_compiler_flag(-wd4514) # MSVC: unreferenced inline function has been removed add_cxx_compiler_flag(-wd4710) # MSVC: function not inlined add_cxx_compiler_flag(-wd4820) # MSVC: padding added after data member target_include_directories(mlt-cpp PUBLIC ${PROJECT_SOURCE_DIR}/include PRIVATE ${PROJECT_SOURCE_DIR}/src ) list(APPEND MLT_INCLUDE_FILES ${PROJECT_SOURCE_DIR}/include/mlt/common.hpp ${PROJECT_SOURCE_DIR}/include/mlt/decoder.hpp ${PROJECT_SOURCE_DIR}/include/mlt/feature.hpp ${PROJECT_SOURCE_DIR}/include/mlt/geometry.hpp ${PROJECT_SOURCE_DIR}/include/mlt/geometry_vector.hpp ${PROJECT_SOURCE_DIR}/include/mlt/layer.hpp ${PROJECT_SOURCE_DIR}/include/mlt/polyfill.hpp ${PROJECT_SOURCE_DIR}/include/mlt/projection.hpp ${PROJECT_SOURCE_DIR}/include/mlt/properties.hpp ${PROJECT_SOURCE_DIR}/include/mlt/tile.hpp ${PROJECT_SOURCE_DIR}/include/mlt/metadata/stream.hpp ${PROJECT_SOURCE_DIR}/include/mlt/metadata/tileset.hpp ${PROJECT_SOURCE_DIR}/include/mlt/util/buffer_stream.hpp ${PROJECT_SOURCE_DIR}/include/mlt/util/packed_bitset.hpp ${PROJECT_SOURCE_DIR}/include/mlt/util/noncopyable.hpp ${PROJECT_SOURCE_DIR}/include/mlt/util/stl.hpp ${PROJECT_SOURCE_DIR}/include/mlt/util/varint.hpp ) list(APPEND MLT_SRC_FILES ${PROJECT_SOURCE_DIR}/src/mlt/decoder.cpp ${PROJECT_SOURCE_DIR}/src/mlt/decode/geometry.hpp ${PROJECT_SOURCE_DIR}/src/mlt/decode/int.cpp ${PROJECT_SOURCE_DIR}/src/mlt/decode/int.hpp ${PROJECT_SOURCE_DIR}/src/mlt/decode/property.hpp ${PROJECT_SOURCE_DIR}/src/mlt/decode/string.hpp ${PROJECT_SOURCE_DIR}/src/mlt/feature.cpp ${PROJECT_SOURCE_DIR}/src/mlt/geometry_vector.cpp ${PROJECT_SOURCE_DIR}/src/mlt/layer.cpp ${PROJECT_SOURCE_DIR}/src/mlt/metadata/stream.cpp ${PROJECT_SOURCE_DIR}/src/mlt/metadata/tileset.cpp ${PROJECT_SOURCE_DIR}/src/mlt/properties.cpp ${PROJECT_SOURCE_DIR}/src/mlt/util/morton_curve.hpp ${PROJECT_SOURCE_DIR}/src/mlt/util/raw.hpp ${PROJECT_SOURCE_DIR}/src/mlt/util/rle.cpp ${PROJECT_SOURCE_DIR}/src/mlt/util/rle.hpp ${PROJECT_SOURCE_DIR}/src/mlt/util/space_filling_curve.hpp ${PROJECT_SOURCE_DIR}/src/mlt/util/vectorized.hpp ${PROJECT_SOURCE_DIR}/src/mlt/util/zigzag.hpp ) if(MLT_WITH_JSON) list(APPEND MLT_INCLUDE_FILES ${PROJECT_SOURCE_DIR}/include/mlt/json.hpp) endif(MLT_WITH_JSON) target_sources(mlt-cpp PRIVATE ${MLT_INCLUDE_FILES} ${MLT_SRC_FILES} ) include("${PROJECT_SOURCE_DIR}/vendor/fastpfor.cmake") # json if(MLT_WITH_JSON) message(STATUS "[MLT] Including JSON support") add_subdirectory("${PROJECT_SOURCE_DIR}/vendor/json" "${CMAKE_CURRENT_BINARY_DIR}/json" EXCLUDE_FROM_ALL SYSTEM) set(json_SOURCE_DIR "${PROJECT_SOURCE_DIR}/vendor/json") target_include_directories(mlt-cpp PUBLIC "${json_SOURCE_DIR}/include") target_compile_definitions(mlt-cpp PUBLIC MLT_WITH_JSON=1) else() message(STATUS "[MLT] No JSON support") endif(MLT_WITH_JSON) if(MLT_WITH_TESTS) include(CTest) add_subdirectory(${PROJECT_SOURCE_DIR}/test EXCLUDE_FROM_ALL) endif(MLT_WITH_TESTS) if(MLT_WITH_TOOLS) add_subdirectory(${PROJECT_SOURCE_DIR}/tool EXCLUDE_FROM_ALL) endif(MLT_WITH_TOOLS) export(TARGETS ${MLT_EXPORT_TARGETS} FILE MLTTargets.cmake) ================================================ FILE: cpp/CTestCustom.cmake ================================================ # Exclude vendor/third-party code from coverage reports set(CTEST_CUSTOM_COVERAGE_EXCLUDE ${CTEST_CUSTOM_COVERAGE_EXCLUDE} ".*/vendor/.*" ".*/build/.*/_deps/.*" ".*/test/.*" ) ================================================ FILE: cpp/README.md ================================================ # maplibre-tile-spec/cpp A C++ implementation of the MapLibre Tile (MLT) vector tile format. ## Status Decoder only, partial support for encodings. CMake and Bazel build support. ## Build ```bash git submodule update --init --recursive cmake -GNinja -Bbuild -S. cmake --build build --target mlt-cpp-test mlt-cpp-json ``` ## Test ``` build/test/mlt-cpp-test ``` ## Use To decode a tile: - Deserialize the tileset metadata - Create a `Decoder` - Call `decode` with the metadata and a view on the raw tile data To use the standard packing of metadata and data within a tile, use `decodeTile`. ```cpp #include #include ... auto metadata = mlt::metadata::tileset::read({metadataBuffer.data(), metadataBuffer.size()}); mlt::Decoder decoder; const auto tile = decoder.decode({mltBuffer.data(), mltBuffer.size()}, metadata); const auto tile2 = decoder.decodeTile({tileData.data(), tileData.size()}); ``` ## Tools A simple application which dumps a tile/metadata file pair to JSON format. ```bash build/tool/mlt-cpp-json ../test/expected/tag0x01/bing/4-12-6.mlt ``` ================================================ FILE: cpp/bazel/check/BUILD.bazel ================================================ load("@rules_cc//cc:cc_binary.bzl", "cc_binary") cc_binary( name = "sse42", srcs = ["sse42.c"], copts = select({ "@platforms//os:windows": [ "/EHsc", "/arch:SSE2", ], "//conditions:default": [ "-march=native", "-msse4.2", ], }), ) cc_binary( name = "avx", srcs = ["avx.c"], copts = select({ "@platforms//os:windows": [ "/EHsc", "/arch:AVX", ], "//conditions:default": [ "-march=native", "-mavx", ], }), ) cc_binary( name = "avx2", srcs = ["avx2.c"], copts = select({ "@platforms//os:windows": [ "/EHsc", "/arch:AVX2", ], "//conditions:default": [ "-march=native", "-mavx2", ], }), ) ================================================ FILE: cpp/bazel/check/avx.c ================================================ #include int main(){ __m128 x=_mm_set1_ps(0.5); x=_mm_permute_ps(x,1); return _mm_movemask_ps(x); } ================================================ FILE: cpp/bazel/check/avx2.c ================================================ #include int main(){ __m256i x=_mm256_set1_epi32(5); x=_mm256_add_epi32(x,x); return _mm256_movemask_epi8(x); } ================================================ FILE: cpp/bazel/check/sse42.c ================================================ #include int main(){ __m128 x=_mm_set1_ps(0.5); x=_mm_dp_ps(x,x,0x77); return _mm_movemask_ps(x); }") ================================================ FILE: cpp/cmake/AddCXXCompilerFlag.cmake ================================================ # - Adds a compiler flag if it is supported by the compiler # # This function checks that the supplied compiler flag is supported and then # adds it to the corresponding compiler flags # # add_cxx_compiler_flag( []) # # - Example # # include(AddCXXCompilerFlag) # add_cxx_compiler_flag(-Wall) # add_cxx_compiler_flag(-no-strict-aliasing RELEASE) # Requires CMake 2.6+ if(__add_cxx_compiler_flag) return() endif() set(__add_cxx_compiler_flag INCLUDED) include(CheckCXXCompilerFlag) function(mangle_compiler_flag FLAG OUTPUT) string(TOUPPER "HAVE_CXX_FLAG_${FLAG}" SANITIZED_FLAG) string(REPLACE "+" "X" SANITIZED_FLAG ${SANITIZED_FLAG}) string(REGEX REPLACE "[^A-Za-z_0-9]" "_" SANITIZED_FLAG ${SANITIZED_FLAG}) string(REGEX REPLACE "_+" "_" SANITIZED_FLAG ${SANITIZED_FLAG}) set(${OUTPUT} "${SANITIZED_FLAG}" PARENT_SCOPE) endfunction(mangle_compiler_flag) function(add_cxx_compiler_flag FLAG) mangle_compiler_flag("${FLAG}" MANGLED_FLAG) set(OLD_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${FLAG}") check_cxx_compiler_flag("${FLAG}" ${MANGLED_FLAG}) set(CMAKE_REQUIRED_FLAGS "${OLD_CMAKE_REQUIRED_FLAGS}") if(${MANGLED_FLAG}) set(VARIANT ${ARGV1}) if(ARGV1) string(TOUPPER "_${VARIANT}" VARIANT) endif() set(CMAKE_CXX_FLAGS${VARIANT} "${CMAKE_CXX_FLAGS${VARIANT}} ${BENCHMARK_CXX_FLAGS${VARIANT}} ${FLAG}" PARENT_SCOPE) endif() endfunction() function(add_required_cxx_compiler_flag FLAG) mangle_compiler_flag("${FLAG}" MANGLED_FLAG) set(OLD_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${FLAG}") check_cxx_compiler_flag("${FLAG}" ${MANGLED_FLAG}) set(CMAKE_REQUIRED_FLAGS "${OLD_CMAKE_REQUIRED_FLAGS}") if(${MANGLED_FLAG}) set(VARIANT ${ARGV1}) if(ARGV1) string(TOUPPER "_${VARIANT}" VARIANT) endif() set(CMAKE_CXX_FLAGS${VARIANT} "${CMAKE_CXX_FLAGS${VARIANT}} ${FLAG}" PARENT_SCOPE) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${FLAG}" PARENT_SCOPE) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${FLAG}" PARENT_SCOPE) set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${FLAG}" PARENT_SCOPE) set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${FLAG}" PARENT_SCOPE) else() message(FATAL_ERROR "Required flag '${FLAG}' is not supported by the compiler") endif() endfunction() function(check_cxx_warning_flag FLAG) mangle_compiler_flag("${FLAG}" MANGLED_FLAG) set(OLD_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") # Add -Werror to ensure the compiler generates an error if the warning flag # doesn't exist. set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -Werror ${FLAG}") check_cxx_compiler_flag("${FLAG}" ${MANGLED_FLAG}) set(CMAKE_REQUIRED_FLAGS "${OLD_CMAKE_REQUIRED_FLAGS}") endfunction() ================================================ FILE: cpp/include/mlt/common.hpp ================================================ #pragma once #include #include #include namespace mlt { using DataView = std::string_view; template constexpr std::size_t countof(T (&)[N]) { return N; } /// `std::underlying_type` that doesn't fail when given a simple type template > struct underlying_type { using type = T; }; template struct underlying_type : ::std::underlying_type {}; template using underlying_type_t = typename underlying_type::type; } // namespace mlt ================================================ FILE: cpp/include/mlt/coordinate.hpp ================================================ #pragma once #include #include namespace mlt { struct Coordinate { float x; float y; Coordinate(float x_, float y_) noexcept : x(x_), y(y_) {} bool operator==(const Coordinate& other) const noexcept { return x == other.x && y == other.y; } bool operator!=(const Coordinate& other) const noexcept { return !(*this == other); } }; using CoordVec = std::vector; struct TileCoordinate { std::uint32_t x; std::uint32_t y; std::uint32_t z; }; } // namespace mlt ================================================ FILE: cpp/include/mlt/decoder.hpp ================================================ #pragma once #include #include #include #include #include namespace mlt { class Decoder : public util::noncopyable { public: using Geometry = geometry::Geometry; using GeometryFactory = geometry::GeometryFactory; Decoder(bool supportFastPFOR); Decoder(std::unique_ptr&&, bool supportFastPFOR); ~Decoder() noexcept; Decoder(Decoder&&) = delete; Decoder& operator=(Decoder&&) = delete; /// Decode a tile MapLibreTile decode(DataView); MapLibreTile decode(BufferStream); private: struct Impl; std::unique_ptr impl; }; } // namespace mlt ================================================ FILE: cpp/include/mlt/feature.hpp ================================================ #pragma once #include #include #include #include namespace mlt { namespace geometry { class Geometry; } class Layer; class Feature : public util::noncopyable { public: using id_t = std::uint64_t; using extent_t = std::uint32_t; using Geometry = geometry::Geometry; Feature() = delete; Feature(Feature&&) noexcept = default; Feature& operator=(Feature&&) = delete; /// Construct a feature /// @param id Feature identifier, or nullopt if the feature has no ID /// @param geometry Feature geometry, required /// @param index Index of the property in the layer Feature(std::optional id, std::unique_ptr&&, std::uint32_t index); ~Feature() noexcept; std::optional getID() const noexcept { return ident; } std::uint32_t getIndex() const noexcept { return index; } const Geometry& getGeometry() const noexcept { return *geometry; } std::optional getProperty(const std::string& key, const Layer&) const; private: std::optional ident; std::uint32_t index; // index of the property in the layer std::unique_ptr geometry; }; } // namespace mlt ================================================ FILE: cpp/include/mlt/geometry.hpp ================================================ #pragma once #include #include #include #include #include #include namespace mlt::geometry { class Geometry : public util::noncopyable { public: using GeometryType = metadata::tileset::GeometryType; protected: Geometry(GeometryType type_) noexcept : type(type_) {} public: virtual ~Geometry() noexcept = default; const auto& getTriangles() const { return triangles; } void setTriangles(std::span triangles_) noexcept { triangles = triangles_; } const metadata::tileset::GeometryType type; private: std::span triangles; }; class Point : public Geometry { public: Point(const Coordinate& coord) noexcept : Geometry(GeometryType::POINT), coordinate(coord) {} const Coordinate& getCoordinate() const noexcept { return coordinate; } private: const Coordinate coordinate; }; class MultiPoint : public Geometry { public: MultiPoint(CoordVec coords) noexcept : Geometry(GeometryType::MULTIPOINT), coordinates(std::move(coords)) {} const CoordVec& getCoordinates() const noexcept { return coordinates; } protected: MultiPoint(CoordVec coords, GeometryType type_) noexcept : Geometry(type_), coordinates(std::move(coords)) {} private: CoordVec coordinates; }; class LineString : public MultiPoint { public: LineString(CoordVec coords) noexcept : MultiPoint(std::move(coords), GeometryType::LINESTRING) {} private: }; class LinearRing : public MultiPoint { public: LinearRing(CoordVec coords) noexcept : MultiPoint(std::move(coords)) {} private: }; class MultiLineString : public Geometry { public: MultiLineString(std::vector lineStrings_) noexcept : Geometry(GeometryType::MULTILINESTRING), lineStrings(std::move(lineStrings_)) {} const std::vector& getLineStrings() const noexcept { return lineStrings; } private: std::vector lineStrings; }; class Polygon : public Geometry { public: using Ring = CoordVec; using RingVec = std::vector; Polygon(RingVec rings_) noexcept : Geometry(GeometryType::POLYGON), rings(std::move(rings_)) {} const RingVec& getRings() const noexcept { return rings; } private: RingVec rings; }; class MultiPolygon : public Geometry { public: using Ring = CoordVec; using RingVec = std::vector; MultiPolygon(std::vector polygons_) noexcept : Geometry(GeometryType::MULTIPOLYGON), polygons(std::move(polygons_)) {} const std::vector& getPolygons() const noexcept { return polygons; } private: std::vector polygons; }; class GeometryFactory { public: GeometryFactory() = default; virtual ~GeometryFactory() = default; virtual std::unique_ptr createPoint(const Coordinate& coord) const { return std::make_unique(coord); } virtual std::unique_ptr createMultiPoint(CoordVec&& coords) const { return std::make_unique(std::move(coords)); } virtual std::unique_ptr createLineString(CoordVec&& coords) const { return std::make_unique(std::move(coords)); } virtual std::unique_ptr createLinearRing(CoordVec&& coords) const { return std::make_unique(std::move(coords)); } virtual std::unique_ptr createPolygon(std::vector&& rings) const { return std::make_unique(std::move(rings)); } virtual std::unique_ptr createMultiLineString(std::vector&& lineStrings) const { return std::make_unique(std::move(lineStrings)); } virtual std::unique_ptr createMultiPolygon(std::vector&& polys) const { return std::make_unique(std::move(polys)); } }; } // namespace mlt::geometry ================================================ FILE: cpp/include/mlt/geometry_vector.hpp ================================================ #pragma once #include #include #include #include namespace mlt::geometry { struct MortonSettings { unsigned numBits; unsigned coordinateShift; }; enum class VertexBufferType : std::uint32_t { MORTON, VEC_2, VEC_3 }; struct TopologyVector : public util::noncopyable { TopologyVector(std::vector&& geometryOffsets_, std::vector&& partOffsets_, std::vector&& ringOffsets_) noexcept : geometryOffsets(geometryOffsets_), partOffsets(partOffsets_), ringOffsets(ringOffsets_) {} const std::vector& getGeometryOffsets() const noexcept { return geometryOffsets; } const std::vector& getPartOffsets() const noexcept { return partOffsets; } const std::vector& getRingOffsets() const noexcept { return ringOffsets; } private: std::vector geometryOffsets; std::vector partOffsets; std::vector ringOffsets; }; struct GeometryVector : public util::noncopyable { using GeometryType = metadata::tileset::GeometryType; virtual ~GeometryVector() = default; std::uint32_t getNumGeometries() const noexcept { return numGeometries; } bool isSingleGeometryType() const noexcept { return singleType; } virtual GeometryType getGeometryType(std::size_t index) const noexcept = 0; virtual bool containsPolygonGeometry() const noexcept = 0; std::vector> getGeometries(const GeometryFactory&) const; protected: GeometryVector(std::uint32_t numGeometries_, bool singleType_, std::vector&& indexBuffer_, std::vector&& vertexBuffer_, VertexBufferType vertexBufferType_, std::vector&& vertexOffsets_, std::vector&& triangleCounts_, std::optional&& topologyVector_, std::optional mortonSettings_ = {}) noexcept : numGeometries(numGeometries_), scale(1.0f), singleType(singleType_), indexBuffer(std::move(indexBuffer_)), vertexBuffer(std::move(vertexBuffer_)), vertexBufferType(vertexBufferType_), vertexOffsets(std::move(vertexOffsets_)), triangleCounts(std::move(triangleCounts_)), topologyVector(std::move(topologyVector_)), mortonSettings(mortonSettings_) {} void applyTriangles(Geometry&, std::uint32_t& triangleOffset, std::uint32_t& indexBufferOffset, std::uint32_t totalVertices, bool multiPolygon) const; protected: std::uint32_t numGeometries; float scale; bool singleType; std::vector indexBuffer; std::vector vertexBuffer; VertexBufferType vertexBufferType; std::vector vertexOffsets; std::vector triangleCounts; std::optional topologyVector; std::optional mortonSettings; }; struct GpuVector : public GeometryVector { GpuVector(std::uint32_t numGeometries_, bool singleType_, std::vector&& triangleCounts_, std::vector&& indexBuffer_, std::vector&& vertexBuffer_, VertexBufferType vertexBufferType_, std::optional&& topologyVector_) noexcept : GeometryVector(numGeometries_, singleType_, std::move(indexBuffer_), std::move(vertexBuffer_), vertexBufferType_, {}, std::move(triangleCounts_), std::move(topologyVector_)) {} private: }; struct ConstGpuVector : public GpuVector { ConstGpuVector(std::uint32_t numGeometries_, GeometryType geometryType_, std::vector&& triangleCounts_, std::vector&& indexBuffer_, std::vector&& vertexBuffer_, VertexBufferType vertexBufferType_, std::optional&& topologyVector_) noexcept : GpuVector(numGeometries_, /*singleType=*/true, std::move(triangleCounts_), std::move(indexBuffer_), std::move(vertexBuffer_), vertexBufferType_, std::move(topologyVector_)), geometryType(geometryType_) {} GeometryType getGeometryType(std::size_t) const noexcept override { return geometryType; } bool containsPolygonGeometry() const noexcept override { return geometryType == GeometryType::POLYGON || geometryType == GeometryType::MULTIPOLYGON; } private: GeometryType geometryType; }; struct FlatGpuVector : public GpuVector { FlatGpuVector(std::vector&& geometryTypes_, std::vector&& triangleCounts_, std::vector&& indexBuffer_, std::vector&& vertexBuffer_, std::optional&& topologyVector_ = {}) noexcept : GpuVector(static_cast(geometryTypes_.size()), /*singleType=*/false, std::move(triangleCounts_), std::move(indexBuffer_), std::move(vertexBuffer_), VertexBufferType::VEC_2, std::move(topologyVector_)), geometryTypes(std::move(geometryTypes_)) {} GeometryType getGeometryType(std::size_t index) const noexcept override { assert(index < geometryTypes.size()); return geometryTypes[index]; } bool containsPolygonGeometry() const noexcept override { // TODO: cache? types can't change return std::ranges::any_of(geometryTypes, [](const auto type) { return type == GeometryType::POLYGON || type == GeometryType::MULTIPOLYGON; }); } private: std::vector geometryTypes; }; struct CpuVector : public GeometryVector { CpuVector(std::uint32_t numGeometries_, bool singleType_, TopologyVector&& topologyVector_, std::vector&& vertexOffsets_, std::vector&& vertexBuffer_, VertexBufferType vertexBufferType_, std::optional mortonSettings_) noexcept : GeometryVector(numGeometries_, singleType_, /*indexBuffer=*/{}, std::move(vertexBuffer_), vertexBufferType_, std::move(vertexOffsets_), {}, std::move(topologyVector_), mortonSettings_) {} Coordinate getVertex(std::size_t index) const { if (!vertexOffsets.empty() && !mortonSettings) { const auto vertexOffset = vertexOffsets[index] * 2; return {static_cast(vertexBuffer[vertexOffset]), static_cast(vertexBuffer[vertexOffset + 1])}; } if (!vertexOffsets.empty() && mortonSettings) { // TODO: move decoding of the morton codes on the GPU in the vertex shader const auto vertexOffset = vertexOffsets[index]; const auto mortonEncodedVertex = vertexBuffer[vertexOffset]; // TODO: improve performance -> inline calculation and move to decoding of VertexBuffer return util::MortonCurve::decode( mortonEncodedVertex, mortonSettings->numBits, mortonSettings->coordinateShift); } return {static_cast(vertexBuffer[2 * index]), static_cast(vertexBuffer[(2 * index) + 1])}; } }; struct ConstGeometryVector : public CpuVector { ConstGeometryVector(std::uint32_t numGeometries_, GeometryType geometryType_, VertexBufferType vertexBufferType_, TopologyVector&& topologyVector_, std::vector&& vertexOffsets_, std::vector&& vertexBuffer_, std::optional mortonSettings_) noexcept : CpuVector(numGeometries_, /*singleType=*/true, std::move(topologyVector_), std::move(vertexOffsets_), std::move(vertexBuffer_), vertexBufferType_, mortonSettings_), geometryType(geometryType_) {} GeometryType getGeometryType(std::size_t) const noexcept override { return geometryType; } bool containsPolygonGeometry() const noexcept override { return geometryType == GeometryType::POLYGON || geometryType == GeometryType::MULTIPOLYGON; } private: GeometryType geometryType; }; struct FlatGeometryVector : public CpuVector { FlatGeometryVector(std::vector&& geometryTypes_, TopologyVector&& topologyVector_, std::vector&& vertexOffsets_, std::vector&& vertexBuffer_, VertexBufferType vertexBufferType_, std::optional mortonSettings_) noexcept : CpuVector(static_cast(geometryTypes_.size()), /*singleType=*/false, std::move(topologyVector_), std::move(vertexOffsets_), std::move(vertexBuffer_), vertexBufferType_, mortonSettings_), geometryTypes(geometryTypes_) {} GeometryType getGeometryType(std::size_t index) const noexcept override { assert(index < geometryTypes.size()); return geometryTypes[index]; } bool containsPolygonGeometry() const noexcept override { // TODO: cache? types can't change return std::ranges::any_of(geometryTypes, [](const auto type) { return type == GeometryType::POLYGON || type == GeometryType::MULTIPOLYGON; }); } private: std::vector geometryTypes; }; } // namespace mlt::geometry ================================================ FILE: cpp/include/mlt/json.hpp ================================================ #pragma once #include #if MLT_WITH_JSON #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mlt { namespace json { using json = nlohmann::json; namespace detail { #pragma region JSON utils /// Create a json array object with pre-allocated space inline json buildArray(std::size_t reservedSize) { json array = json::array(); assert(array.is_array()); array.get_ref().reserve(reservedSize); return array; } /// Add to a json array the result of a function applied to each element in a range template requires requires(TFunc f, typename std::ranges::range_rvalue_reference_t v) { { f(v) } -> std::same_as; } inline json append(TRange sourceRange, json&& array, TFunc transform) { assert(array.is_array()); std::ranges::transform( std::forward(sourceRange), std::back_inserter(array), std::forward(transform)); return std::move(array); } /// Convert a collection range into a json array by applying the given function to each element template requires requires(TFunc f, typename std::ranges::range_rvalue_reference_t v) { { f(v) } -> std::same_as; } inline json buildArray(TRange sourceRange, TFunc transform) { return append(sourceRange, buildArray(std::ranges::size(sourceRange)), std::forward(transform)); } #pragma endregion JSON utils #pragma region Geometry /// Build the coordinate representation for a single coordinate that has already been projected inline json buildProjectedCoordinateArray(const Coordinate& coord) { return json::array({coord.x, coord.y}); } /// Build the coordinate representation for a single coordinate inline json buildCoordinateArray(const Coordinate& coord, const Projection& projection) { return buildProjectedCoordinateArray(projection.project(coord)); } /// Build the coordinate representation for a collection of coordinates inline json buildCoordinatesArray(const CoordVec& coords, const Projection& projection) { return buildArray(coords, [&](const auto& coord) { return buildCoordinateArray(coord, projection); }); } /// Build the coordinate representation for a polygon, consisting of the rings concatenated to the shell inline json buildPolygonCoords(const std::vector& polyRings, const Projection& projection) { auto result = buildArray(polyRings.size()); return append(polyRings, std::move(result), [&](const auto& lineString) { return buildCoordinatesArray(lineString, projection); }); } /// Create the value type for the "geometry" entry in a GeoJSON feature with the given coordinate representation inline json buildGeometryElement(std::string_view type, json&& coords) { return { {"type", type}, {"coordinates", std::move(coords)}, }; } inline json buildGeometryElement(const geometry::Point& point, const Projection& projection, bool geoJSON) { if (geoJSON) { return { {"type", "Point"}, {"coordinates", buildCoordinateArray(point.getCoordinate(), projection)}, }; } else { std::ostringstream ss; ss << "POINT (" << point.getCoordinate().x << " " << point.getCoordinate().y << ")"; return ss.str(); } } inline json buildGeometryElement(const geometry::LineString& line, const Projection& projection, bool geoJSON) { if (geoJSON) { return buildGeometryElement("LineString", buildCoordinatesArray(line.getCoordinates(), projection)); } else { std::ostringstream ss; ss << "LINESTRING ("; const auto& coords = line.getCoordinates(); for (std::size_t i = 0, n = coords.size(); i < n; ++i) { ss << (i == 0 ? "" : ", ") << coords[i].x << " " << coords[i].y; } ss << ")"; return ss.str(); } } inline json buildGeometryElement(const geometry::LinearRing& line, const Projection& projection, bool geoJSON) { if (geoJSON) { return buildGeometryElement("LineString", buildCoordinatesArray(line.getCoordinates(), projection)); } else { std::ostringstream ss; ss << "LINESTRING ("; const auto& coords = line.getCoordinates(); for (std::size_t i = 0; i < coords.size(); ++i) { ss << (i == 0 ? "" : ", ") << coords[i].x << " " << coords[i].y; } ss << ")"; return ss.str(); } } inline json buildGeometryElement(const geometry::MultiPoint& points, const Projection& projection, bool geoJSON) { if (geoJSON) { return buildGeometryElement("MultiPoint", buildCoordinatesArray(points.getCoordinates(), projection)); } else { std::ostringstream ss; ss << "MULTIPOINT ("; const auto& coords = points.getCoordinates(); for (std::size_t i = 0; i < coords.size(); ++i) { ss << (i == 0 ? "" : ", ") << "(" << coords[i].x << " " << coords[i].y << ")"; } ss << ")"; return ss.str(); } } inline json buildGeometryElement(const geometry::MultiLineString& mls, const Projection& projection, bool geoJSON) { if (geoJSON) { return buildGeometryElement("MultiLineString", buildArray(mls.getLineStrings(), [&](const auto& lineString) { return buildCoordinatesArray(lineString, projection); })); } else { std::ostringstream ss; ss << "MULTILINESTRING ("; const auto& lineStrings = mls.getLineStrings(); for (std::size_t i = 0, n = lineStrings.size(); i < n; ++i) { if (i != 0) { ss << ", "; } ss << "("; const auto& coords = lineStrings[i]; for (std::size_t j = 0; j < coords.size(); ++j) { ss << (j == 0 ? "" : ", ") << coords[j].x << " " << coords[j].y; } ss << ")"; } ss << ")"; return ss.str(); } } inline json buildGeometryElement(const geometry::Polygon& poly, const Projection& projection, bool geoJSON) { if (geoJSON) { return buildGeometryElement("Polygon", buildPolygonCoords(poly.getRings(), projection)); } else { std::ostringstream ss; ss << "POLYGON ("; const auto& rings = poly.getRings(); for (std::size_t i = 0, n = rings.size(); i < n; ++i) { if (i != 0) { ss << ", "; } ss << "("; const auto& coords = rings[i]; for (std::size_t j = 0; j <= coords.size(); ++j) { // Wrap around to the first coordinate to close the ring. // See `getLineStringCoords` const auto& coord = coords[j % coords.size()]; ss << (j == 0 ? "" : ", ") << coord.x << " " << coord.y; } ss << ")"; } ss << ")"; return ss.str(); } } inline json buildGeometryElement(const geometry::MultiPolygon& poly, const Projection& projection, bool geoJSON) { if (geoJSON) { return buildGeometryElement("MultiPolygon", buildArray(poly.getPolygons(), [&](const auto& poly) { return buildPolygonCoords(poly, projection); })); } else { std::ostringstream ss; ss << "MULTIPOLYGON ("; const auto& polygons = poly.getPolygons(); for (std::size_t i = 0; i < polygons.size(); ++i) { if (i != 0) { ss << ", "; } ss << "("; const auto& rings = polygons[i]; for (std::size_t j = 0; j < rings.size(); ++j) { if (j != 0) { ss << ", "; } ss << "("; const auto& coords = rings[j]; for (std::size_t k = 0; k <= coords.size(); ++k) { const auto& coord = coords[k % coords.size()]; ss << (k == 0 ? "" : ", ") << coord.x << " " << coord.y; } ss << ")"; } ss << ")"; } ss << ")"; return ss.str(); } } inline json buildAnyGeometryElement(const geometry::Geometry& geometry, const Projection& projection, bool geoJSON) { switch (geometry.type) { case metadata::tileset::GeometryType::POINT: return buildGeometryElement(static_cast(geometry), projection, geoJSON); case metadata::tileset::GeometryType::LINESTRING: return buildGeometryElement(static_cast(geometry), projection, geoJSON); case metadata::tileset::GeometryType::POLYGON: return buildGeometryElement(static_cast(geometry), projection, geoJSON); case metadata::tileset::GeometryType::MULTIPOINT: return buildGeometryElement(static_cast(geometry), projection, geoJSON); case metadata::tileset::GeometryType::MULTILINESTRING: return buildGeometryElement(static_cast(geometry), projection, geoJSON); case metadata::tileset::GeometryType::MULTIPOLYGON: return buildGeometryElement(static_cast(geometry), projection, geoJSON); default: throw std::runtime_error("Unsupported geometry type " + std::to_string(std::to_underlying(geometry.type))); } } #pragma endregion Geometry #pragma region Properties struct PropertyVisitor { template std::optional encodeFloatingPoint(T value, std::string_view prefix) const { if (std::isfinite(value)) { return json(value); } if (std::isnan(value)) { return json(std::string(prefix) + "::NAN"); } if (std::signbit(value)) { return json(std::string(prefix) + "::NEG_INFINITY"); } return json(std::string(prefix) + "::INFINITY"); } std::optional operator()(float value) const { return encodeFloatingPoint(value, "f32"); } std::optional operator()(double value) const { return encodeFloatingPoint(value, "f64"); } template std::optional operator()(const T& value) const { return value; } }; inline json buildProperties(const Layer& layer, const Feature& feature) { auto result = json::object(); for (const auto& [key, _] : layer.getProperties()) { if (const auto property = feature.getProperty(key, layer); property) { if (auto json = std::visit(PropertyVisitor(), *property); json) { result[key] = std::move(*json); } } } return result; } #pragma endregion Properties } // namespace detail inline json toJSON(const Layer& layer, const Feature& feature, const Projection& projection, bool geoJSON) { auto result = json{ {"geometry", detail::buildAnyGeometryElement(feature.getGeometry(), projection, geoJSON)}, }; if (const auto id = feature.getID(); id.has_value()) { result["id"] = *id; } if (geoJSON) { result["type"] = "Feature"; } if (!layer.getProperties().empty()) { result["properties"] = detail::buildProperties(layer, feature); } return result; } inline json toJSON(const Layer& layer, const TileCoordinate& tileCoord, bool geoJSON) { const auto projection = Projection{layer.getExtent(), tileCoord}; const auto features = std::ranges::views::all(layer.getFeatures()); return {{"name", layer.getName()}, {"extent", layer.getExtent()}, {"features", detail::buildArray(features, [&](const auto& feature) { return toJSON(layer, feature, projection, geoJSON); })}}; } inline json toJSON(const MapLibreTile& tile, const TileCoordinate& tileCoord, bool geoJSON) { const auto layers = std::ranges::views::all(tile.getLayers()); return { {"layers", detail::buildArray(layers, [&](const auto& layer) { return toJSON(layer, tileCoord, geoJSON); })}, }; } } // namespace json } // namespace mlt #endif ================================================ FILE: cpp/include/mlt/layer.hpp ================================================ #pragma once #include #include #include #include namespace mlt { namespace geometry { struct GeometryVector; } class Layer : public util::noncopyable { public: using extent_t = std::uint32_t; Layer() = delete; Layer(std::string name_, extent_t extent_, std::unique_ptr&& geometryVector_, std::vector features_, PropertyVecMap properties_) noexcept; ~Layer(); Layer(Layer&&) noexcept = default; Layer& operator=(Layer&&) = default; const std::string& getName() const noexcept { return name; } extent_t getExtent() const noexcept { return extent; } const std::vector& getFeatures() const noexcept { return features; } const PropertyVecMap& getProperties() const { return properties; } private: std::string name; extent_t extent; // Retain the geometry vector because features may reference data from it rather than making copies std::unique_ptr geometryVector; std::vector features; PropertyVecMap properties; }; } // namespace mlt ================================================ FILE: cpp/include/mlt/metadata/stream.hpp ================================================ #pragma once #include #include #include #include #include #include namespace mlt::metadata::stream { enum class DictionaryType : std::uint32_t { NONE = 0, SINGLE = 1, SHARED = 2, VERTEX = 3, MORTON = 4, FSST = 5, VALUE_COUNT = 6, }; enum class LengthType : std::uint32_t { VAR_BINARY = 0, GEOMETRIES = 1, PARTS = 2, RINGS = 3, TRIANGLES = 4, SYMBOL = 5, DICTIONARY = 6, VALUE_COUNT = 7, }; enum class PhysicalLevelTechnique : std::uint32_t { NONE = 0, /// Preferred, tends to produce the best compression ratio and decoding performance. /// But currently limited to 32-bit integer. FAST_PFOR = 1, /// Can produce better results in combination with a heavyweight compression scheme like Gzip. /// Simple compression scheme where the decoder are easier to implement compared to FastPfor. VARINT = 2, VALUE_COUNT = 3, }; enum class LogicalLevelTechnique : std::uint32_t { NONE = 0, DELTA = 1, COMPONENTWISE_DELTA = 2, RLE = 3, MORTON = 4, PSEUDODECIMAL = 5, VALUE_COUNT = 6, }; enum class OffsetType : std::uint32_t { VERTEX = 0, INDEX = 1, STRING = 2, KEY = 3, VALUE_COUNT = 4, }; enum class PhysicalStreamType : std::uint32_t { PRESENT = 0, DATA = 1, OFFSET = 2, LENGTH = 3, VALUE_COUNT = 4, }; class LogicalStreamType : public util::noncopyable { public: LogicalStreamType(DictionaryType type) noexcept : dictionaryType(type) {} LogicalStreamType(OffsetType type) noexcept : offsetType(type) {} LogicalStreamType(LengthType type) noexcept : lengthType(type) {} LogicalStreamType() = delete; LogicalStreamType(LogicalStreamType&&) noexcept = default; LogicalStreamType& operator=(LogicalStreamType&&) noexcept = default; const std::optional& getDictionaryType() const noexcept { return dictionaryType; } const std::optional& getOffsetType() const noexcept { return offsetType; } const std::optional& getLengthType() const noexcept { return lengthType; } private: std::optional dictionaryType; std::optional offsetType; std::optional lengthType; }; class StreamMetadata : public util::noncopyable { public: StreamMetadata() = delete; StreamMetadata(PhysicalStreamType physicalStreamType_, std::optional logicalStreamType_, LogicalLevelTechnique logicalLevelTechnique1_, LogicalLevelTechnique logicalLevelTechnique2_, PhysicalLevelTechnique physicalLevelTechnique_, std::uint32_t numValues_, std::uint32_t byteLength_) noexcept : physicalStreamType(physicalStreamType_), logicalStreamType(std::move(logicalStreamType_)), logicalLevelTechnique1(logicalLevelTechnique1_), logicalLevelTechnique2(logicalLevelTechnique2_), physicalLevelTechnique(physicalLevelTechnique_), numValues(numValues_), byteLength(byteLength_) {} virtual ~StreamMetadata() noexcept = default; StreamMetadata(StreamMetadata&&) noexcept = default; StreamMetadata& operator=(StreamMetadata&&) noexcept = default; virtual LogicalLevelTechnique getMetadataType() const noexcept { return LogicalLevelTechnique::NONE; } static std::unique_ptr decode(BufferStream&); PhysicalStreamType getPhysicalStreamType() const { return physicalStreamType; } const std::optional& getLogicalStreamType() const { return logicalStreamType; } LogicalLevelTechnique getLogicalLevelTechnique1() const { return logicalLevelTechnique1; } LogicalLevelTechnique getLogicalLevelTechnique2() const { return logicalLevelTechnique2; } PhysicalLevelTechnique getPhysicalLevelTechnique() const { return physicalLevelTechnique; } std::uint32_t getNumValues() const noexcept { return numValues; } std::uint32_t getByteLength() const noexcept { return byteLength; } private: int getLogicalType() const noexcept; friend class RleEncodedStreamMetadata; friend class MortonEncodedStreamMetadata; friend std::unique_ptr decode(BufferStream&); static StreamMetadata decodeInternal(BufferStream&); PhysicalStreamType physicalStreamType; std::optional logicalStreamType; LogicalLevelTechnique logicalLevelTechnique1; LogicalLevelTechnique logicalLevelTechnique2; PhysicalLevelTechnique physicalLevelTechnique; // After logical Level technique was applied -> when rle is used it is the length of the runs and values array std::uint32_t numValues; std::uint32_t byteLength; }; class RleEncodedStreamMetadata : public StreamMetadata { public: /** * Only used for RLE encoded integer values, not boolean and byte values. * * @param numValues After LogicalLevelTechnique was applied -> numRuns + numValues * @param runs Length of the runs array * @param numRleValues Used for pre-allocating the arrays on the client for faster decoding */ RleEncodedStreamMetadata(PhysicalStreamType physicalStreamType_, std::optional logicalStreamType_, LogicalLevelTechnique logicalLevelTechnique1_, LogicalLevelTechnique logicalLevelTechnique2_, PhysicalLevelTechnique physicalLevelTechnique_, unsigned numValues_, unsigned byteLength_, unsigned runs_, unsigned numRleValues_) noexcept : StreamMetadata(physicalStreamType_, std::move(logicalStreamType_), logicalLevelTechnique1_, logicalLevelTechnique2_, physicalLevelTechnique_, numValues_, byteLength_), runs(runs_), numRleValues(numRleValues_) {} RleEncodedStreamMetadata(StreamMetadata&& streamMetadata, unsigned runs_, unsigned numRleValues_) noexcept : StreamMetadata(std::move(streamMetadata)), runs(runs_), numRleValues(numRleValues_) {} RleEncodedStreamMetadata() = delete; LogicalLevelTechnique getMetadataType() const noexcept override { return LogicalLevelTechnique::RLE; } static RleEncodedStreamMetadata decodePartial(StreamMetadata&& streamMetadata, BufferStream& tileData) { const auto [runs, numValues] = util::decoding::decodeVarints(tileData); return RleEncodedStreamMetadata(std::move(streamMetadata), runs, numValues); } static RleEncodedStreamMetadata decode(BufferStream& tileData) { return decodePartial(decodeInternal(tileData), tileData); } unsigned getRuns() const noexcept { return runs; } unsigned getNumRleValues() const noexcept { return numRleValues; } private: unsigned runs; unsigned numRleValues; }; class MortonEncodedStreamMetadata : public StreamMetadata { public: MortonEncodedStreamMetadata(PhysicalStreamType physicalStreamType_, LogicalStreamType logicalStreamType_, LogicalLevelTechnique logicalLevelTechnique1_, LogicalLevelTechnique logicalLevelTechnique2_, PhysicalLevelTechnique physicalLevelTechnique_, unsigned numValues_, unsigned byteLength_, unsigned numBits_, unsigned coordinateShift_) noexcept : StreamMetadata(physicalStreamType_, std::move(logicalStreamType_), logicalLevelTechnique1_, logicalLevelTechnique2_, physicalLevelTechnique_, numValues_, byteLength_), numBits(numBits_), coordinateShift(coordinateShift_) {} MortonEncodedStreamMetadata(StreamMetadata&& streamMetadata, int numBits_, int coordinateShift_) noexcept : StreamMetadata(std::move(streamMetadata)), numBits(numBits_), coordinateShift(coordinateShift_) {} LogicalLevelTechnique getMetadataType() const noexcept override { return LogicalLevelTechnique::MORTON; } static MortonEncodedStreamMetadata decodePartial(StreamMetadata&& streamMetadata, BufferStream& tileData) { const auto [numBits, coordShift] = util::decoding::decodeVarints(tileData); return MortonEncodedStreamMetadata(std::move(streamMetadata), numBits, coordShift); } static MortonEncodedStreamMetadata decode(BufferStream& tileData) { return decodePartial(decodeInternal(tileData), tileData); } unsigned getNumBits() const noexcept { return numBits; } unsigned getCoordinateShift() const noexcept { return coordinateShift; } private: unsigned numBits; unsigned coordinateShift; }; class PdeEncodedMetadata {}; } // namespace mlt::metadata::stream ================================================ FILE: cpp/include/mlt/metadata/tileset.hpp ================================================ #pragma once #include #include #include #include #include namespace mlt { struct BufferStream; } namespace mlt::metadata::tileset { // See https://maplibre.org/maplibre-tile-spec/specification/ namespace schema { enum class ColumnScope { // 1:1 Mapping of property and feature -> id and geometry FEATURE = 0, // For M-Values -> 1:1 Mapping for property and vertex VERTEX = 1, }; enum class ScalarType { BOOLEAN = 0, INT_8 = 1, UINT_8 = 2, INT_32 = 3, UINT_32 = 4, INT_64 = 5, UINT_64 = 6, FLOAT = 7, DOUBLE = 8, STRING = 9, }; enum class ComplexType { // vec2 for the VertexBuffer stream with additional information // (streams) about the topology GEOMETRY = 0, // vec3 for the VertexBuffer stream with additional information // (streams) about the topology STRUCT = 1, }; enum class LogicalScalarType { // uin32 or 64_t depending on hasLongID ID = 0, }; enum class LogicalComplexType { }; } // namespace schema using schema::ColumnScope; using schema::ComplexType; using schema::LogicalComplexType; using schema::LogicalScalarType; using schema::ScalarType; enum class GeometryType { POINT = 0, LINESTRING = 1, POLYGON = 2, MULTIPOINT = 3, MULTILINESTRING = 4, MULTIPOLYGON = 5, }; struct ScalarColumn { std::variant type; bool hasLongID = false; bool hasPhysicalType() const { return std::holds_alternative(type); } bool hasLogicalType() const { return std::holds_alternative(type); } auto& getPhysicalType() { return std::get(type); } auto& getPhysicalType() const { return std::get(type); } auto& getLogicalType() { return std::get(type); } auto& getLogicalType() const { return std::get(type); } bool isID() const { return hasLogicalType() && getLogicalType() == LogicalScalarType::ID; } }; // The type tree is flattened in to a list via a pre-order traversal. // Represents a column if it is a root (top-level) type or a child of a nested type. struct Column; struct ComplexColumn { std::variant type; // The complex type Geometry and the logical type BINARY have no children // since there layout is implicit known. RangeMap has only one child // specifying the type of the value since the key is always a vec2. std::vector children; bool hasChildren() const { return !children.empty(); } bool hasPhysicalType() const { return std::holds_alternative(type); } bool hasLogicalType() const { return std::holds_alternative(type); } auto& getPhysicalType() { return std::get(type); } auto& getPhysicalType() const { return std::get(type); } auto& getLogicalType() { return std::get(type); } auto& getLogicalType() const { return std::get(type); } bool isGeometry() const { return hasPhysicalType() && getPhysicalType() == ComplexType::GEOMETRY; } bool isStruct() const { return hasPhysicalType() && getPhysicalType() == ComplexType::STRUCT; } }; // Column are top-level types in the schema struct Column { std::string name; bool nullable = false; ColumnScope columnScope = ColumnScope::FEATURE; std::variant type; bool hasScalarType() const { return std::holds_alternative(type); } bool hasComplexType() const { return std::holds_alternative(type); } auto& getScalarType() { return std::get(type); } auto& getScalarType() const { return std::get(type); } auto& getComplexType() { return std::get(type); } auto& getComplexType() const { return std::get(type); } bool isID() const { return hasScalarType() && getScalarType().isID(); } bool isGeometry() const { return hasComplexType() && getComplexType().isGeometry(); } bool isStruct() const { return hasComplexType() && getComplexType().isStruct(); } }; struct FeatureTable { std::string name; std::uint32_t extent; std::vector columns; }; FeatureTable decodeFeatureTable(BufferStream&); } // namespace mlt::metadata::tileset ================================================ FILE: cpp/include/mlt/metadata/type_map.hpp ================================================ #include #include #include #include namespace mlt::metadata::type_map { struct Tag0x01 { using Column = metadata::tileset::Column; using ScalarColumn = metadata::tileset::ScalarColumn; using ComplexColumn = metadata::tileset::ComplexColumn; using ScalarType = metadata::tileset::ScalarType; using ComplexType = metadata::tileset::ComplexType; using LogicalScalarType = metadata::tileset::LogicalScalarType; using LogicalComplexType = metadata::tileset::LogicalComplexType; /// Produces the unique type encoding for a `Column` or `Field` static std::optional encodeColumnType(std::optional physicalScalarType, std::optional logicalScalarType, std::optional physicalComplexType, std::optional, bool isNullable, bool hasChildren, bool hasLongIDs) { if (physicalScalarType) { if (!hasChildren) { return mapScalarType(*physicalScalarType, isNullable); } } else if (logicalScalarType) { if (logicalScalarType == LogicalScalarType::ID) { return isNullable ? (hasLongIDs ? 3 : 2) : (hasLongIDs ? 1 : 0); } } else if (physicalComplexType) { if (physicalComplexType == ComplexType::GEOMETRY) { if (!isNullable && !hasChildren) { return 4; } } else if (physicalComplexType == ComplexType::STRUCT) { if (!isNullable && hasChildren) { return 30; } } } return std::nullopt; } /// Re-create a `Column` from the unique type code. /// The inverse of `encodeColumnType` static std::optional decodeColumnType(std::uint32_t typeCode) { using ColumnScope = metadata::tileset::ColumnScope; switch (typeCode) { case 0: case 1: case 2: case 3: return Column{.name = {}, .nullable = (typeCode & 1) != 0, .columnScope = ColumnScope::FEATURE, .type = ScalarColumn{.type = LogicalScalarType::ID, .hasLongID = typeCode > 1}}; case 4: return Column{.name = {}, .nullable = false, .columnScope = ColumnScope::FEATURE, .type = ComplexColumn{.type = ComplexType::GEOMETRY, .children = {}}}; case 30: return Column{.name = {}, .nullable = false, .columnScope = ColumnScope::FEATURE, .type = ComplexColumn{.type = ComplexType::STRUCT, .children = {}}}; default: if (const auto type = mapScalarType(typeCode); type) { return Column{.name = {}, .nullable = (typeCode & 1) != 0, .columnScope = ColumnScope::FEATURE, .type = ScalarColumn{.type = *type}}; } return std::nullopt; } } static bool columnTypeHasName(std::uint32_t typeCode) { return (10 <= typeCode); } static bool columnTypeHasChildren(std::uint32_t typeCode) { return (typeCode == 30); } static bool hasStreamCount(const Column& column) { if (column.hasScalarType()) { if (column.getScalarType().hasPhysicalType()) { switch (column.getScalarType().getPhysicalType()) { default: case ScalarType::BOOLEAN: case ScalarType::INT_8: case ScalarType::UINT_8: case ScalarType::INT_32: case ScalarType::UINT_32: case ScalarType::INT_64: case ScalarType::UINT_64: case ScalarType::FLOAT: case ScalarType::DOUBLE: return false; case ScalarType::STRING: return true; }; } else if (column.getScalarType().hasLogicalType()) { if (column.getScalarType().getLogicalType() == LogicalScalarType::ID) { return false; } } } else if (column.hasComplexType()) { if (column.getComplexType().hasPhysicalType()) { switch (column.getComplexType().getPhysicalType()) { case ComplexType::GEOMETRY: case ComplexType::STRUCT: return true; default: break; } } } // other cases should be impossible assert(false); return false; } private: constexpr static std::optional mapScalarType(std::uint32_t typeCode) { switch (typeCode) { case 10: case 11: return ScalarType::BOOLEAN; case 12: case 13: return ScalarType::INT_8; case 14: case 15: return ScalarType::UINT_8; case 16: case 17: return ScalarType::INT_32; case 18: case 19: return ScalarType::UINT_32; case 20: case 21: return ScalarType::INT_64; case 22: case 23: return ScalarType::UINT_64; case 24: case 25: return ScalarType::FLOAT; case 26: case 27: return ScalarType::DOUBLE; case 28: case 29: return ScalarType::STRING; default: return std::nullopt; } } constexpr static std::optional mapScalarType(ScalarType typeCode, bool isNullable) { switch (typeCode) { case ScalarType::BOOLEAN: return isNullable ? 11 : 10; case ScalarType::INT_8: return isNullable ? 13 : 12; case ScalarType::UINT_8: return isNullable ? 15 : 14; case ScalarType::INT_32: return isNullable ? 17 : 16; case ScalarType::UINT_32: return isNullable ? 19 : 18; case ScalarType::INT_64: return isNullable ? 21 : 20; case ScalarType::UINT_64: return isNullable ? 23 : 22; case ScalarType::FLOAT: return isNullable ? 25 : 24; case ScalarType::DOUBLE: return isNullable ? 27 : 26; case ScalarType::STRING: return isNullable ? 29 : 28; default: assert(false); return std::nullopt; } } }; } // namespace mlt::metadata::type_map ================================================ FILE: cpp/include/mlt/polyfill.hpp ================================================ #pragma once #include #include #include // IWYU pragma: keep - Needed by MSVC #include namespace std { #if !__has_cpp_attribute(__cpp_lib_to_underlying) template constexpr auto to_underlying(E e) noexcept { return static_cast>(e); } #endif #if !__has_cpp_attribute(__cpp_lib_byteswap) template constexpr T byteswap(T value) noexcept { static_assert(std::has_unique_object_representations_v, "T may not have padding bits"); auto value_representation = std::bit_cast>(value); std::ranges::reverse(value_representation); return std::bit_cast(value_representation); } #endif } // namespace std ================================================ FILE: cpp/include/mlt/projection.hpp ================================================ #pragma once #include #include #include #include #include #include namespace mlt { // Intended to match the results of // https://github.com/mapbox/vector-tile-js/blob/77851380b63b07fd0af3d5a3f144cc86fb39fdd1/lib/vectortilefeature.js#L129 class Projection { public: Projection() = delete; Projection(const Projection&) noexcept = default; Projection(Projection&&) noexcept = default; Projection(Layer::extent_t extent, const TileCoordinate& tile) : size(extent * (1 << tile.z)), x0(extent * tile.x), y0(extent * tile.y), s1(360.0 / size) { if (extent == 0) { throw std::runtime_error("Invalid tile extent"); } } Coordinate project(const Coordinate& coord) const noexcept { return {projectX(coord.x), projectY(coord.y)}; } private: float projectX(float x) const noexcept { return ((x + x0) * s1) - 180.0f; } float projectY(float y) const noexcept { return (2.0f * radToDeg(std::atan(std::exp(degToRad(180.0f - ((y + y0) * s1)))))) - 90.0f; } static float degToRad(float deg) noexcept { return deg * std::numbers::pi / 180.0; } static float radToDeg(float rad) noexcept { return rad * 180 / std::numbers::pi; } std::uint64_t size; std::uint64_t x0; std::uint64_t y0; double s1; constexpr static double s2 = 360.0 / std::numbers::pi; }; } // namespace mlt ================================================ FILE: cpp/include/mlt/properties.hpp ================================================ #pragma once #include #include #include #include #include #include #include #include #include namespace mlt { /// A block of data and a collection of strings views on it class StringDictViews : util::noncopyable { public: StringDictViews() = default; StringDictViews(std::vector&& data_, std::vector views_) noexcept : data(std::move(data_)), views(std::move(views_)) {} StringDictViews(std::shared_ptr> data_, std::vector views_) noexcept : sharedData(std::move(data_)), views(std::move(views_)) {} StringDictViews(StringDictViews&&) noexcept = default; StringDictViews& operator=(StringDictViews&&) = default; const auto& getStrings() const noexcept { return views; } private: std::vector data; std::shared_ptr> sharedData; std::vector views; }; /// A single feature property. /// String properties reference the source property vector and must not outlive it. using Property = std::variant, std::int32_t, std::optional, std::int64_t, std::optional, std::uint32_t, std::optional, std::uint64_t, std::optional, float, std::optional, double, std::optional, std::string_view>; /// Map of properties for a single feature using PropertyMap = std::unordered_map; /// A single property for a column, with one value per feature using PropertyVec = std::variant, std::vector, std::vector, std::vector, std::vector, std::vector, std::vector, StringDictViews>; namespace detail { struct PropertyCounter { const bool byteIsBoolean; template std::size_t operator()(const std::vector& vec) const noexcept { return vec.size(); } std::size_t operator()(const std::vector& vec) const noexcept { // For boolean columns, each bit is a property return vec.size() * (byteIsBoolean ? 8 : 1); } std::size_t operator()(const StringDictViews& views) const noexcept { return views.getStrings().size(); } }; } // namespace detail static inline std::size_t propertyCount(const PropertyVec& vec, bool byteIsBoolean) { return std::visit(detail::PropertyCounter{byteIsBoolean}, vec); } /// A column of properties and the present bits for each feature class PresentProperties : public util::noncopyable { public: using ScalarType = metadata::tileset::ScalarType; PresentProperties() = delete; PresentProperties(ScalarType type_, PropertyVec properties_, const PackedBitset& present) noexcept; ScalarType getType() const noexcept { return type; } bool isBoolean() const noexcept { return type == ScalarType::BOOLEAN; } const PropertyVec& getProperties() const noexcept { return properties; } std::size_t getPropertyCount() const { return propertyCount(properties, isBoolean()); } std::optional getProperty(std::uint32_t logicalIndex) const; private: ScalarType type; PropertyVec properties; using ByteIndexVec = std::vector; using ShortIndexVec = std::vector; using IntIndexVec = std::vector; std::variant physicalIndexes; }; /// All the property columns for a layer using PropertyVecMap = std::unordered_map; } // namespace mlt ================================================ FILE: cpp/include/mlt/tile.hpp ================================================ #pragma once #include #include #include namespace mlt { class MapLibreTile : public util::noncopyable { public: MapLibreTile() = delete; MapLibreTile(std::vector layers_) noexcept : layers(std::move(layers_)) {} MapLibreTile(MapLibreTile&&) noexcept = default; MapLibreTile& operator=(MapLibreTile&&) noexcept = default; const std::vector& getLayers() const noexcept { return layers; } const Layer* getLayer(const std::string_view& name) const noexcept { auto hit = std::ranges::find_if(layers, [&](const auto& layer) { return layer.getName() == name; }); return (hit != layers.end()) ? &*hit : nullptr; } private: std::vector layers; }; } // namespace mlt ================================================ FILE: cpp/include/mlt/util/buffer_stream.hpp ================================================ #pragma once #include #include #include #include #include namespace mlt { struct BufferStream : public util::noncopyable { BufferStream() = delete; BufferStream(DataView data_) noexcept : data(std::move(data_)), offset(0) {} BufferStream(BufferStream&&) = delete; BufferStream& operator=(BufferStream&&) = delete; auto getSize() const noexcept { return data.size(); } auto getOffset() const noexcept { return offset; } auto getRemaining() const noexcept { return data.size() - offset; } bool available(std::size_t size = 1) const noexcept { return size <= getRemaining(); } /// Get another DataView representing a portion of the remaining data BufferStream getSubStream(std::size_t offset, std::size_t length) const { const auto remaining = getRemaining(); if (offset + length > remaining) { throw std::runtime_error("Substream exceeds buffer size"); } return {{getReadPosition() + offset, length}}; } void reset() { offset = 0; } void reset(DataView data_) { data = std::move(data_); offset = 0; } template const T* getData() const noexcept { return reinterpret_cast(data.data()); } template const T* getReadPosition() const noexcept { return reinterpret_cast(&data[offset]); } template DataView::value_type read() { check(sizeof(T)); const T* p = getReadPosition(); consume(sizeof(T)); return static_cast(*p); } void read(void* buffer, std::size_t size) { check(size); std::memcpy(buffer, getReadPosition(), size); consume(size); } template DataView::value_type peek() const { check(sizeof(T)); return *getReadPosition(); } void consume(std::size_t count) { check(count); offset += count; } private: void check(std::size_t count) const { if (!available(count)) { throw std::runtime_error("Unexpected end of buffer"); } } DataView data; std::size_t offset; }; } // namespace mlt ================================================ FILE: cpp/include/mlt/util/noncopyable.hpp ================================================ #pragma once namespace mlt::util { class noncopyable { public: noncopyable(noncopyable&) = delete; noncopyable(noncopyable&&) = default; noncopyable& operator=(const noncopyable&) = delete; noncopyable& operator=(noncopyable&&) noexcept = default; protected: constexpr noncopyable() noexcept = default; ~noncopyable() noexcept = default; }; } // namespace mlt::util ================================================ FILE: cpp/include/mlt/util/packed_bitset.hpp ================================================ #pragma once #include #include #include #include #include #include namespace mlt { // `std::vector` is not great, just use a vector of bytes using PackedBitset = std::vector; /// Test a specific bit static inline bool testBit(const PackedBitset& bitset, std::size_t i) noexcept { return ((i / 8) < bitset.size()) && (bitset[i / 8] & (1 << (i % 8))); } /// Get the total number of bits set static inline std::size_t countSetBits(const PackedBitset& bitset) { // NOLINTNEXTLINE(boost-use-ranges) return std::accumulate( bitset.begin(), bitset.end(), static_cast(0), [](const auto total, const auto byte) { return total + static_cast(std::popcount(byte)); }); } /// Return the index of the next set bit within the bitstream /// @param bits The bitset /// @param afterIndex The bit index to start with /// @return The index of the next set bit (including the starting index) static inline std::optional nextSetBit(const PackedBitset& bits, const std::size_t afterIndex = 0) noexcept { if (std::size_t byteIndex = (afterIndex / 8); byteIndex < bits.size()) { auto byte = bits[byteIndex]; // If we're mid-byte, shift it down so the next bit is in the 1 position std::size_t result = afterIndex; if (const auto partialBits = result & 7; partialBits) { byte >>= partialBits; if (!byte) { // skip to the next byte if (++byteIndex == bits.size()) { return {}; } result += (8 - partialBits); byte = bits[byteIndex]; } } while (byteIndex < bits.size()) { // If this byte is non-zero, the next bit is within it if (byte) { const auto ffs = std::countr_zero(byte); return result + ffs; } // Continue to the next byte if (++byteIndex < bits.size()) { byte = bits[byteIndex]; result += 8; } } } return {}; } } // namespace mlt ================================================ FILE: cpp/include/mlt/util/stl.hpp ================================================ #pragma once #include #include #include #include namespace mlt::util { /// Create a vector of N items by invoking the given function N times template requires requires(F f, I i) { { f(i) } -> std::same_as; } std::vector generateVector(const std::size_t count, F generator) { std::vector result; result.reserve(count); std::generate_n( std::back_inserter(result), count, [i = I{0}, f = std::move(generator)]() mutable { return f(i++); }); return result; } // Helper for using lambdas with `std::variant` // See https://en.cppreference.com/w/cpp/utility/variant/visit template struct overloaded : Ts... { using Ts::operator()...; }; // explicit deduction guide (not needed as of C++20) // (but seems to be needed by MSVC) template overloaded(Ts...) -> overloaded; } // namespace mlt::util ================================================ FILE: cpp/include/mlt/util/varint.hpp ================================================ #pragma once #include #include #include #include namespace mlt::util::decoding { /// Returns the size of the varint encoding for a given value. /// Equivalent to `max(1,ceil(log128(value)))` template std::size_t getVarintSize(T value) { return std::max(1, ((8 * sizeof(value)) - std::countl_zero(value) + 6) / 7); } template requires(std::is_integral_v) T decodeVarint(BufferStream&); // allow decoding directly to enum types template requires(std::is_enum_v) T decodeVarint(BufferStream& tileData) { return static_cast(decodeVarint>>(tileData)); } template <> inline std::uint32_t decodeVarint(BufferStream& buffer) { // Max 4 bytes supported auto b = buffer.read(); auto value = static_cast(b & 0x7f); if (b & 0x80) { b = buffer.read(); value |= static_cast(b & 0x7f) << 7; if (b & 0x80) { b = buffer.read(); value |= static_cast(b & 0x7f) << 14; if (b & 0x80) { b = buffer.read(); value |= static_cast(b & 0x7f) << 21; if (b & 0x80) { const auto v = static_cast(buffer.read() & 0x7f); if (v > 0x0f) { throw std::runtime_error("varint exceeds 32 bits"); } value |= v << 28; } } } } return value; } template <> inline std::uint64_t decodeVarint(BufferStream& buffer) { std::uint64_t value = 0; for (int shift = 0; buffer.available();) { auto b = buffer.read(); value |= static_cast(b & 0x7F) << shift; if (shift == 63 && b > 1) { throw std::runtime_error("Varint too long"); } if ((b & 0x80) == 0) { break; } shift += 7; if (shift >= 64) { throw std::runtime_error("Varint too long"); } } return value; } /// Decode N varints, retrurning the values in a `std::tuple` template requires(std::is_integral_v || std::is_enum_v, 0 < N) auto decodeVarints(BufferStream& buffer) { auto v = std::make_tuple(static_cast(decodeVarint(buffer))); if constexpr (N == 1) { return v; } else return std::tuple_cat(std::move(v), decodeVarints(buffer)); } /// Decode N varints into the provided buffer /// Each result is cast to the target type. template requires(std::is_integral_v && (std::is_integral_v || std::is_enum_v) && sizeof(TDecode) <= sizeof(TTarget)) void decodeVarints(BufferStream& buffer, const std::uint32_t numValues, TTarget* out) { std::generate_n(out, numValues, [&buffer]() { return static_cast(decodeVarint(buffer)); }); } } // namespace mlt::util::decoding ================================================ FILE: cpp/mod.just ================================================ just := quote(just_executable()) _default: (just '--list' 'cpp') [private] just *args: {{just}} {{args}} # Initialize CMake build cmake-init: cmake -B build -S . -DCMAKE_BUILD_TYPE=Coverage # Build with CMake cmake-build: cmake-init cmake --build build --target mlt-cpp-test mlt-cpp-json # Quick compile check (CMake build) check: cmake-build # Run all CI steps: test, coverage, bazel build ci-test: coverage bazel-build {{just}} assert-git-is-clean # Delete build artifacts clean: rm -rf build # Reformat code fmt: pre-commit run --all-files clang-format # Run linting lint: echo "TODO: Add C++ linting command" _install_npm_deps: cd ../test/synthetic/synthetic-test-utils && npm ci npm ci _test-synthetic: _install_npm_deps {{just}} ts install npx vitest run tool/synthetic.test.ts # Run tests [working-directory: 'build'] test: _clean_coverage_data cmake-build _test-synthetic ctest _clean_coverage_data: find . -name "*.gcda" -delete # Generate coverage report [working-directory: 'build'] coverage: _clean_coverage_data (_check-tool 'gcovr') test gcovr --root ../.. \ --filter ../src --filter ../include \ --txt coverage.txt \ --merge-mode-functions=separate \ --cobertura-pretty --cobertura coverage.xml \ --html-details coverage.html @echo "Coverage report at $PWD/coverage.html" # Build with Bazel bazel-build: bazel build //cpp:mlt_cpp # Sanity test for mlt-cpp-json tool test-json: build/tool/mlt-cpp-json ../test/expected/tag0x01/bing/4-12-6.mlt | jq . >/dev/null _check-tool command: @which {{command}} > /dev/null || (echo "Error: {{command}} is not installed. Please install it using: brew install {{command}} (macOS) or pip3 install {{command}} (Linux)" && exit 1) ================================================ FILE: cpp/package.json ================================================ { "type": "module", "dependencies": { "synthetic-test-utils": "file:../test/synthetic/synthetic-test-utils", "vitest": "^4.0.18" } } ================================================ FILE: cpp/src/mlt/decode/geometry.hpp ================================================ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include namespace mlt::decoder { class GeometryDecoder { public: using GeometryVector = geometry::GeometryVector; GeometryDecoder(IntegerDecoder& intDecoder) : intDecoder(intDecoder) {} private: enum class VectorType : std::uint32_t { FLAT, CONST, SEQUENCE, DICTIONARY, FSST_DICTIONARY, }; VectorType getVectorTypeIntStream(const metadata::stream::StreamMetadata& streamMetadata) { using namespace metadata::stream; const auto logicalLevelTechnique1 = streamMetadata.getLogicalLevelTechnique1(); const auto logicalLevelTechnique2 = streamMetadata.getLogicalLevelTechnique2(); const auto metadataType = streamMetadata.getMetadataType(); const auto& rleMetadata = static_cast(streamMetadata); const auto rleRuns = (metadataType == LogicalLevelTechnique::RLE) ? rleMetadata.getRuns() : 0; if (logicalLevelTechnique1 == LogicalLevelTechnique::RLE) { assert(metadataType == LogicalLevelTechnique::RLE); return (rleRuns == 1) ? VectorType::CONST : VectorType::FLAT; } else if (logicalLevelTechnique1 == LogicalLevelTechnique::DELTA && logicalLevelTechnique2 == LogicalLevelTechnique::RLE) { assert(metadataType == LogicalLevelTechnique::RLE); // If base value equals delta value then one run else two runs if (rleRuns == 1 || rleRuns == 2) { return VectorType::SEQUENCE; } } return (streamMetadata.getNumValues() == 1) ? VectorType::CONST : VectorType::FLAT; } public: std::unique_ptr decodeGeometryColumn(BufferStream& tileData, const metadata::tileset::Column& column, std::uint32_t numStreams) { using namespace util::decoding; using namespace metadata::stream; using namespace metadata::tileset; std::vector geometryTypes; std::vector geometryOffsets; std::vector partOffsets; std::vector ringOffsets; std::vector vertexOffsets; std::vector indexBuffer; std::vector triangles; std::vector vertices; const auto geomTypeMetadata = StreamMetadata::decode(tileData); if (!geomTypeMetadata) { throw std::runtime_error("geometry column missing metadata stream: " + column.name); } [[maybe_unused]] const auto geometryTypesVectorType = getVectorTypeIntStream(*geomTypeMetadata); // If all geometries in the column have the same geometry type, we could decode them // somewhat more efficiently, and return the geometry in a more GPU-friendly form. // if (geometryTypesVectorType == VectorType::CONST) { // const auto geomType = intDecoder.decodeConstIntStream( // tileData, *geomTypeMetadata); // ... // } // Different geometry types are mixed in the geometry column intDecoder.decodeIntStream( tileData, geometryTypes, *geomTypeMetadata); std::optional mortonSettings; for (std::uint32_t i = 1; i < numStreams; ++i) { const auto geomStreamMetadata = StreamMetadata::decode(tileData); switch (geomStreamMetadata->getPhysicalStreamType()) { case PhysicalStreamType::LENGTH: { if (!geomStreamMetadata->getLogicalStreamType() || !geomStreamMetadata->getLogicalStreamType()->getLengthType()) { throw std::runtime_error("Length stream missing logical type: " + column.name); } const auto type = *geomStreamMetadata->getLogicalStreamType()->getLengthType(); std::optional>> target; switch (type) { case LengthType::GEOMETRIES: target = geometryOffsets; break; case LengthType::PARTS: target = partOffsets; break; case LengthType::RINGS: target = ringOffsets; break; case LengthType::TRIANGLES: target = triangles; break; default: throw std::runtime_error("Length stream type '" + std::to_string(std::to_underlying(type)) + " not implemented: " + column.name); } intDecoder.decodeIntStream( tileData, target->get(), *geomStreamMetadata); break; } case PhysicalStreamType::OFFSET: { if (!geomStreamMetadata->getLogicalStreamType() || !geomStreamMetadata->getLogicalStreamType()->getOffsetType()) { throw std::runtime_error("Offset stream missing type: " + column.name); } const auto type = *geomStreamMetadata->getLogicalStreamType()->getOffsetType(); std::optional>> target; switch (type) { case OffsetType::VERTEX: target = vertexOffsets; break; case OffsetType::INDEX: target = indexBuffer; break; default: throw std::runtime_error("Offset stream type '" + std::to_string(std::to_underlying(type)) + " not implemented: " + column.name); } intDecoder.decodeIntStream(tileData, target->get(), *geomStreamMetadata); break; } case PhysicalStreamType::DATA: { if (!geomStreamMetadata->getLogicalStreamType() || !geomStreamMetadata->getLogicalStreamType()->getDictionaryType()) { throw std::runtime_error("Data stream missing dictionary type: " + column.name); } if (mortonSettings) { throw std::runtime_error("multiple data streams"); } const auto type = *geomStreamMetadata->getLogicalStreamType()->getDictionaryType(); switch (type) { case DictionaryType::VERTEX: switch (geomStreamMetadata->getPhysicalLevelTechnique()) { case PhysicalLevelTechnique::NONE: case PhysicalLevelTechnique::VARINT: case PhysicalLevelTechnique::FAST_PFOR: intDecoder.decodeIntStream( tileData, vertices, *geomStreamMetadata, /*isSigned=*/true); break; default: throw std::runtime_error("Unsupported encoding " + std::to_string(std::to_underlying( geomStreamMetadata->getPhysicalLevelTechnique())) + " for geometries: " + column.name); }; break; case DictionaryType::MORTON: { assert(geomStreamMetadata->getMetadataType() == LogicalLevelTechnique::MORTON); const auto& mortonStreamMetadata = static_cast( *geomStreamMetadata); // TODO: This results in double-morton-decoding, need to align with TypeScript // implementation. mortonSettings = geometry::MortonSettings{ // .numBits = mortonStreamMetadata.getNumBits(), // .coordinateShift = mortonStreamMetadata.getCoordinateShift()}; intDecoder.decodeMortonStream( tileData, vertices, mortonStreamMetadata); break; } default: throw std::runtime_error("Dictionary type '" + std::to_string(std::to_underlying(type)) + "' not implemented: " + column.name); } break; } case PhysicalStreamType::PRESENT: break; default: throw std::runtime_error("Unsupported logical stream type: " + column.name); } } if (!indexBuffer.empty() && partOffsets.empty()) { /* Case when the indices of a Polygon outline are not encoded in the data so no * topology data are present in the tile */ return std::make_unique( std::move(geometryTypes), std::move(triangles), std::move(indexBuffer), std::move(vertices)); } if (!geometryOffsets.empty()) { auto geometryOffsetsCopy = geometryOffsets; // TODO: avoid copies decodeRootLengthStream(geometryTypes, geometryOffsetsCopy, /*bufferId=*/GeometryType::POLYGON, geometryOffsets); if (!partOffsets.empty()) { if (!ringOffsets.empty()) { auto partOffsetsCopy = partOffsets; decodeLevel1LengthStream(geometryTypes, geometryOffsets, partOffsetsCopy, /*isLineStringPresent=*/false, partOffsets); auto ringOffsetsCopy = ringOffsets; decodeLevel2LengthStream(geometryTypes, geometryOffsets, partOffsets, ringOffsetsCopy, ringOffsets); } else { auto partOffsetsCopy = partOffsets; decodeLevel1WithoutRingBufferLengthStream( geometryTypes, geometryOffsets, partOffsetsCopy, partOffsets); } } } else if (!partOffsets.empty()) { auto partOffsetsCopy = partOffsets; if (!ringOffsets.empty()) { auto ringOffsetsCopy = ringOffsets; decodeRootLengthStream(geometryTypes, partOffsetsCopy, GeometryType::LINESTRING, partOffsets); decodeLevel1LengthStream(geometryTypes, partOffsets, ringOffsetsCopy, /*isLineStringPresent=*/true, ringOffsets); } else { decodeRootLengthStream(geometryTypes, partOffsetsCopy, GeometryType::POINT, partOffsets); } } if (!indexBuffer.empty()) { /* Case when the indices of a Polygon outline are encoded in the tile */ return std::make_unique( std::move(geometryTypes), std::move(triangles), std::move(indexBuffer), std::move(vertices), geometry::TopologyVector(std::move(geometryOffsets), std::move(partOffsets), std::move(ringOffsets))); } return std::make_unique( std::move(geometryTypes), geometry::TopologyVector(std::move(geometryOffsets), std::move(partOffsets), std::move(ringOffsets)), std::move(vertexOffsets), std::move(vertices), mortonSettings ? geometry::VertexBufferType::MORTON : geometry::VertexBufferType::VEC_2, mortonSettings); } /* * Handle the parsing of the different topology length buffers separate not generic to reduce the * branching and improve the performance */ void decodeRootLengthStream(const std::vector& geometryTypes, const std::vector& rootLengthStream, const metadata::tileset::GeometryType bufferId, std::vector& rootBufferOffsets) { assert(&rootLengthStream != &rootBufferOffsets); rootBufferOffsets.resize(geometryTypes.size() + 1); std::uint32_t previousOffset = rootBufferOffsets[0] = 0; std::uint32_t rootLengthCounter = 0; for (std::size_t i = 0; i < geometryTypes.size(); ++i) { /* Test if the geometry has and entry in the root buffer * BufferId: 2 GeometryOffsets -> MultiPolygon, MultiLineString, MultiPoint * BufferId: 1 PartOffsets -> Polygon * BufferId: 0 PartOffsets, RingOffsets -> LineString * */ previousOffset = rootBufferOffsets[i + 1] = previousOffset + ((geometryTypes[i] > bufferId) ? rootLengthStream[rootLengthCounter++] : 1); } } void decodeLevel1LengthStream(const std::vector& geometryTypes, const std::vector& rootOffsetBuffer, const std::vector& level1LengthBuffer, const bool isLineStringPresent, std::vector& level1BufferOffsets) { assert(&rootOffsetBuffer != &level1BufferOffsets); assert(&level1LengthBuffer != &level1BufferOffsets); using metadata::tileset::GeometryType; level1BufferOffsets.resize(rootOffsetBuffer[rootOffsetBuffer.size() - 1] + 1); std::uint32_t previousOffset = level1BufferOffsets[0] = 0; std::uint32_t level1BufferCounter = 1; std::uint32_t level1LengthBufferCounter = 0; for (std::size_t i = 0; i < geometryTypes.size(); ++i) { const auto geometryType = geometryTypes[i]; const auto numGeometries = rootOffsetBuffer[i + 1] - rootOffsetBuffer[i]; if (geometryType == GeometryType::MULTIPOLYGON || geometryType == GeometryType::POLYGON || (isLineStringPresent && (geometryType == GeometryType::MULTILINESTRING || geometryType == GeometryType::LINESTRING))) { /* For MultiPolygon, Polygon and in some cases for MultiLineString and LineString * a value in the level1LengthBuffer exists */ for (std::uint32_t j = 0; j < numGeometries; ++j) { previousOffset = level1BufferOffsets[level1BufferCounter++] = previousOffset + level1LengthBuffer[level1LengthBufferCounter++]; } } else { /* For MultiPoint and Point and in some cases for MultiLineString and LineString no value in the * level1LengthBuffer exists */ for (std::uint32_t j = 0; j < numGeometries; j++) { level1BufferOffsets[level1BufferCounter++] = ++previousOffset; } } } } /* * Case where no ring buffer exists so no MultiPolygon or Polygon geometry is part of the buffer */ void decodeLevel1WithoutRingBufferLengthStream(const std::vector& geometryTypes, const std::vector& rootOffsetBuffer, const std::vector& level1LengthBuffer, std::vector& level1BufferOffsets) { assert(&rootOffsetBuffer != &level1BufferOffsets); assert(&level1LengthBuffer != &level1BufferOffsets); using metadata::tileset::GeometryType; level1BufferOffsets.resize(rootOffsetBuffer[rootOffsetBuffer.size() - 1] + 1); std::uint32_t previousOffset = level1BufferOffsets[0] = 0; std::uint32_t level1OffsetBufferCounter = 1; std::uint32_t level1LengthCounter = 0; for (std::size_t i = 0; i < geometryTypes.size(); ++i) { const auto geometryType = geometryTypes[i]; const auto numGeometries = rootOffsetBuffer[i + 1] - rootOffsetBuffer[i]; if (geometryType == GeometryType::MULTILINESTRING || geometryType == GeometryType::LINESTRING) { /* For MultiLineString and LineString a value in the level1LengthBuffer exists */ for (std::uint32_t j = 0; j < numGeometries; ++j) { previousOffset = level1BufferOffsets[level1OffsetBufferCounter++] = previousOffset + level1LengthBuffer[level1LengthCounter++]; } } else { /* For MultiPoint and Point no value in level1LengthBuffer exists */ for (std::uint32_t j = 0; j < numGeometries; ++j) { level1BufferOffsets[level1OffsetBufferCounter++] = ++previousOffset; } } } } void decodeLevel2LengthStream(const std::vector& geometryTypes, const std::vector& rootOffsetBuffer, const std::vector& level1OffsetBuffer, const std::vector& level2LengthBuffer, std::vector& level2BufferOffsets) { assert(&rootOffsetBuffer != &level2BufferOffsets); assert(&level1OffsetBuffer != &level2BufferOffsets); assert(&level2LengthBuffer != &level2BufferOffsets); using metadata::tileset::GeometryType; level2BufferOffsets.resize(level1OffsetBuffer[level1OffsetBuffer.size() - 1] + 1); std::uint32_t previousOffset = level2BufferOffsets[0] = 0; std::uint32_t level1OffsetBufferCounter = 1; std::uint32_t level2OffsetBufferCounter = 1; std::uint32_t level2LengthBufferCounter = 0; for (std::size_t i = 0; i < geometryTypes.size(); ++i) { const auto geometryType = geometryTypes[i]; const auto numGeometries = rootOffsetBuffer[i + 1] - rootOffsetBuffer[i]; if (geometryType != GeometryType::POINT && geometryType != GeometryType::MULTIPOINT) { /* For MultiPolygon, MultiLineString, Polygon and LineString a value in level2LengthBuffer * exists */ for (std::uint32_t j = 0; j < numGeometries; ++j) { const auto numParts = level1OffsetBuffer[level1OffsetBufferCounter] - level1OffsetBuffer[level1OffsetBufferCounter - 1]; level1OffsetBufferCounter++; for (std::uint32_t k = 0; k < numParts; ++k) { previousOffset = level2BufferOffsets[level2OffsetBufferCounter++] = previousOffset + level2LengthBuffer[level2LengthBufferCounter++]; } } } else { /* For MultiPoint and Point no value in level2LengthBuffer exists */ for (std::uint32_t j = 0; j < numGeometries; j++) { level2BufferOffsets[level2OffsetBufferCounter++] = ++previousOffset; level1OffsetBufferCounter++; } } } } private: IntegerDecoder& intDecoder; }; } // namespace mlt::decoder ================================================ FILE: cpp/src/mlt/decode/int.cpp ================================================ #include #include #include // from fastpfor/... #if MLT_WITH_FASTPFOR #include #include #include #endif // MLT_WITH_FASTPFOR #include #ifdef _MSC_VER #include #include #include #endif namespace mlt::decoder { struct IntegerDecoder::Impl { #if MLT_WITH_FASTPFOR // Impl pattern to prevent FastPFOR from being an API dependency FastPForLib::CompositeCodec, FastPForLib::VariableByte> codec; #endif // MLT_WITH_FASTPFOR }; IntegerDecoder::IntegerDecoder([[maybe_unused]] bool enableFastPFOR) : impl(std::make_unique()) #if MLT_WITH_FASTPFOR , enableFastPFOR(enableFastPFOR) #endif { } IntegerDecoder::~IntegerDecoder() noexcept = default; std::uint32_t IntegerDecoder::decodeFastPfor([[maybe_unused]] BufferStream& buffer, [[maybe_unused]] std::uint32_t* const result, [[maybe_unused]] const std::size_t numValues, [[maybe_unused]] const std::size_t byteLength) { #if MLT_WITH_FASTPFOR if (enableFastPFOR) { #if (defined(_M_IX86) || defined(_M_X64) || defined(_M_IA64) || defined(_M_AMD64)) #if defined(__GNUC__) || defined(__clang__) // https://gcc.gnu.org/onlinedocs/gcc/x86-Built-in-Functions.html if (!__builtin_cpu_supports("sse4.1")) { // The x86 implementation in FastPFOR requires SSE4.1 throw std::runtime_error("FastPFOR decoding requires SSE4.1 on x86 platforms"); } #elif defined(_MSC_VER) // https://learn.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex { int cpui[4]; __cpuid(cpui, 0); cpui[2] = 0; if (cpui[0] > 0) { __cpuidex(cpui, 1, 0); } if (const std::bitset<32> fn1ECX = cpui[2]; !fn1ECX[19]) { // The x86 implementation in FastPFOR requires SSE4.1 throw std::runtime_error("FastPFOR decoding requires SSE4.1 on x86 platforms"); } } #endif #endif const auto* inputValues = reinterpret_cast(buffer.getReadPosition()); // TODO: change to little endian in the encoder? const auto intLength = (byteLength + sizeof(std::uint32_t) - 1) / sizeof(std::uint32_t); const auto leBuffer = getTempBuffer(intLength); std::transform(inputValues, inputValues + intLength, leBuffer.get(), [](std::uint32_t v) noexcept { return std::byteswap(v); }); auto resultCount = numValues; impl->codec.decodeArray(leBuffer, intLength, result, resultCount); buffer.consume(byteLength); return static_cast(resultCount); } else { throw std::runtime_error("FastPFOR decoding is not enabled"); } #else throw std::runtime_error("FastPFOR decoding is not enabled. Configure with MLT_WITH_FASTPFOR=ON"); #endif // MLT_WITH_FASTPFOR } } // namespace mlt::decoder ================================================ FILE: cpp/src/mlt/decode/int.hpp ================================================ #pragma once #include #include #include #include #include #include #include #include #include namespace mlt::decoder { class IntegerDecoder : public util::noncopyable { public: using StreamMetadata = metadata::stream::StreamMetadata; using MortonEncodedStreamMetadata = metadata::stream::MortonEncodedStreamMetadata; IntegerDecoder(bool enableFastPFOR); ~IntegerDecoder() noexcept; IntegerDecoder(IntegerDecoder&&) = delete; // FastPFOR classes have implicitly-deleted assignment operators. // We could create new ones, if really necessary. IntegerDecoder& operator=(IntegerDecoder&&) = delete; /// Decode a buffer of integers into another, according to the encoding scheme specified by the metadata /// @param values Input values /// @param out Output values (should be sized according to `getIntArrayBufferSize`) /// @param outCount Number of elements in the output buffer /// @param metadata Stream metadata specifying the encoding details template requires((std::is_integral_v || std::is_enum_v) && (std::is_integral_v || std::is_enum_v) && sizeof(T) <= sizeof(TTarget)) void decodeIntArray(const T* values, std::size_t count, TTarget* out, std::size_t outCount, const StreamMetadata&, bool isSigned = std::is_signed_v); /// Decode an integer stream into the target buffer /// @param tileData source data /// @param out output data, automatically resized /// @param metadata stream metadata specifying the encoding details /// @details Uses an internal buffer for intermediate values template void decodeIntStream(BufferStream& tileData, std::vector& out, const StreamMetadata&, bool isSigned = std::is_signed_v); /// Decode an integer stream into the target buffer /// @param tileData source data /// @param buffer storage for intermediate values, automatically resized /// @param out output data, automatically resized /// @param metadata stream metadata specifying the encoding details template void decodeIntStream(BufferStream& tileData, TInt* buffer, std::size_t bufferSize, std::vector& out, const StreamMetadata&, bool isSigned = std::is_signed_v); template TTarget decodeConstIntStream(BufferStream& tileData, const StreamMetadata&, bool isSigned = std::is_signed_v); template void decodeMortonStream(BufferStream& tileData, std::vector& out, const MortonEncodedStreamMetadata& metadata); template void decodeMortonStream(BufferStream& tileData, TInt* buffer, std::size_t bufferSize, TTarget* out, std::size_t outCount, const MortonEncodedStreamMetadata&); private: struct Impl; std::unique_ptr impl; std::vector> buffer; std::size_t bufferIndex = 0; #if MLT_WITH_FASTPFOR bool enableFastPFOR; #endif // Very simple RAII wrapper for temporary buffers. // Buffers must be used only within a current method invocation. struct BufWrapperBase : util::noncopyable { BufWrapperBase(IntegerDecoder& decoder_, std::uint8_t* ptr_) noexcept : decoder(decoder_), ptr(ptr_) { if (ptr) { decoder.bufferIndex++; } } BufWrapperBase(BufWrapperBase&& other) noexcept : decoder(other.decoder), ptr(other.ptr) { other.ptr = nullptr; } ~BufWrapperBase() noexcept { if (ptr) { decoder.bufferIndex--; } } IntegerDecoder& decoder; std::uint8_t* ptr; }; template struct BufWrapper : BufWrapperBase { BufWrapper(IntegerDecoder& decoder_, std::uint8_t* ptr_) noexcept : BufWrapperBase(decoder_, ptr_) {} T* get() const noexcept { return reinterpret_cast(ptr); } operator T*() const noexcept { return get(); } }; /// Get a buffer for intermediate values, which must not be retained beyond the current method invocation template BufWrapper getTempBuffer(std::size_t minSize) { buffer.resize(std::max(buffer.size(), bufferIndex + 1)); buffer[bufferIndex].resize(std::max(buffer[bufferIndex].size(), minSize * sizeof(T))); return {*this, buffer[bufferIndex].data()}; } /// Get the size of the buffer necessary for `decodeIntArray` std::size_t getIntArrayBufferSize(const std::size_t count, const StreamMetadata&); template requires(std::is_integral_v && (std::is_integral_v || std::is_enum_v)) void decodeStream(BufferStream& tileData, TTarget* out, std::size_t outSize, const StreamMetadata&); template static void decodeRLE(const std::vector& values, std::vector& out, const std::uint32_t numRuns); template static void decodeDeltaRLE(const std::vector& values, std::vector& out, const std::uint32_t numRuns); /// Decode zigzag-delta values /// @param values Input values /// @param count Count of input values /// @param out Output values, must be the same size as the input /// @param outCount Count of available output values /// @note Input and output may reference the same memory template requires(std::is_integral_v> && std::is_integral_v> && sizeof(T) <= sizeof(TTarget)) static void decodeZigZagDelta(const T* values, const std::size_t count, TTarget* const out, const std::size_t outCount) noexcept; /// Decode standard or delta Morton codes /// @param data Input data /// @param count Number of input values /// @param out Output data, must be 2x the input size /// @param outCount Number of available output values template requires(std::is_integral_v && (std::is_integral_v || std::is_enum_v)) static void decodeMortonCodes(const TDecode* const data, const std::size_t count, TTarget* out, std::size_t outCount, int numBits, int coordinateShift) noexcept; std::uint32_t decodeFastPfor(BufferStream& buffer, std::uint32_t* const result, const std::size_t numValues, const std::size_t byteLength); }; } // namespace mlt::decoder ================================================ FILE: cpp/src/mlt/decode/int_template.hpp ================================================ #pragma once #include #include #include #include #include #include #include #include namespace mlt::decoder { #pragma region Public inline std::size_t IntegerDecoder::getIntArrayBufferSize(const std::size_t count, const StreamMetadata& streamMetadata) { using namespace metadata::stream; switch (streamMetadata.getLogicalLevelTechnique1()) { case LogicalLevelTechnique::DELTA: if (streamMetadata.getLogicalLevelTechnique2() == LogicalLevelTechnique::RLE && streamMetadata.getMetadataType() == LogicalLevelTechnique::RLE) { const auto& rleMetadata = static_cast(streamMetadata); return rleMetadata.getNumRleValues(); } return streamMetadata.getNumValues(); case LogicalLevelTechnique::NONE: case LogicalLevelTechnique::COMPONENTWISE_DELTA: return count; case LogicalLevelTechnique::RLE: { if (streamMetadata.getMetadataType() != LogicalLevelTechnique::RLE) { return 0; } const auto& rleMetadata = static_cast(streamMetadata); return rleMetadata.getNumRleValues(); } case LogicalLevelTechnique::MORTON: { return 2 * count; } case LogicalLevelTechnique::PSEUDODECIMAL: default: return 0; } } template requires((std::is_integral_v || std::is_enum_v) && (std::is_integral_v || std::is_enum_v) && sizeof(T) <= sizeof(TTarget)) void IntegerDecoder::decodeIntArray(const T* values, const std::size_t count, TTarget* out, const std::size_t outCount, const StreamMetadata& streamMetadata, const bool isSigned) { using namespace metadata::stream; using namespace util::decoding; switch (streamMetadata.getLogicalLevelTechnique1()) { case LogicalLevelTechnique::NONE: { assert(count <= outCount); const auto f = isSigned ? [](T x) noexcept { return static_cast(decodeZigZag(x)); } : [](T x) noexcept { return static_cast(x); }; std::transform(values, values + count, out, f); return; } case LogicalLevelTechnique::DELTA: if (streamMetadata.getLogicalLevelTechnique2() == LogicalLevelTechnique::RLE) { if (streamMetadata.getMetadataType() != LogicalLevelTechnique::RLE) { throw std::runtime_error("invalid RLE metadata"); } const auto& rleMetadata = static_cast(streamMetadata); assert(outCount >= rleMetadata.getNumRleValues()); rle::decodeInt(values, count, out, outCount, rleMetadata.getRuns()); decodeZigZagDelta(out, outCount, out, outCount); } else { assert(outCount >= count); decodeZigZagDelta(values, count, out, outCount); } break; case LogicalLevelTechnique::COMPONENTWISE_DELTA: if constexpr (std::is_same_v || std::is_same_v) { assert(count <= outCount); std::transform(values, values + count, out, [](auto x) { return static_cast(x); }); vectorized::decodeComponentwiseDeltaVec2(out, outCount); break; } throw std::runtime_error("Logical level technique COMPONENTWISE_DELTA not implemented for 64-bit values"); case LogicalLevelTechnique::RLE: { if (streamMetadata.getMetadataType() != LogicalLevelTechnique::RLE) { throw std::runtime_error("invalid RLE metadata"); } const auto& rleMetadata = static_cast(streamMetadata); assert(rleMetadata.getNumRleValues() <= outCount); auto decoder = isSigned ? [](T x) { return static_cast(decodeZigZag(x)); } : [](T x) { return static_cast(x); }; rle::decodeInt(values, count, out, outCount, rleMetadata.getRuns(), std::move(decoder)); break; } case LogicalLevelTechnique::MORTON: { // TODO: zig-zag decode when morton second logical level technique if (streamMetadata.getMetadataType() != LogicalLevelTechnique::MORTON) { throw std::runtime_error("invalid RLE metadata"); } const auto& mortonMetadata = static_cast(streamMetadata); assert(2 * count <= outCount); if constexpr (std::is_same_v && std::is_same_v) { decodeMortonCodes( values, count, out, outCount, mortonMetadata.getNumBits(), mortonMetadata.getCoordinateShift()); } else { throw std::runtime_error("Logical level technique MORTON not implemented for 64-bit values"); } break; } case LogicalLevelTechnique::PSEUDODECIMAL: default: throw std::runtime_error("The specified logical level technique is not supported for integers: " + std::to_string(std::to_underlying(streamMetadata.getLogicalLevelTechnique1()))); break; } } template void IntegerDecoder::decodeIntStream(BufferStream& tileData, std::vector& out, const StreamMetadata& metadata, const bool isSigned) { auto tempBuffer = getTempBuffer(metadata.getNumValues()); decodeIntStream(tileData, tempBuffer, metadata.getNumValues(), out, metadata, isSigned); } template void IntegerDecoder::decodeIntStream(BufferStream& tileData, TInt* buffer, std::size_t bufferSize, std::vector& out, const StreamMetadata& metadata, const bool isSigned) { decodeStream(tileData, buffer, bufferSize, metadata); out.resize(getIntArrayBufferSize(bufferSize, metadata)); decodeIntArray(buffer, bufferSize, out.data(), out.size(), metadata, isSigned); } template TTarget IntegerDecoder::decodeConstIntStream(BufferStream& tileData, const StreamMetadata& metadata, const bool isSigned) { TInt buffer[2] = {0}; decodeStream(tileData, &buffer[0], countof(buffer), metadata); if (metadata.getNumValues() < 1 || metadata.getNumValues() > 2) { throw std::runtime_error("unexpected number of values in constant stream"); } if (metadata.getNumValues() == 1) { return isSigned ? decodeZigZagValue(buffer[0]) : buffer[0]; } return isSigned ? decodeZigZagValue(buffer[1]) : buffer[1]; } template void IntegerDecoder::decodeMortonStream(BufferStream& tileData, std::vector& out, const MortonEncodedStreamMetadata& metadata) { auto tempBuffer = getTempBuffer(metadata.getNumValues()); out.resize(2 * metadata.getNumValues()); decodeMortonStream( tileData, tempBuffer, metadata.getNumValues(), out.data(), out.size(), metadata); } template void IntegerDecoder::decodeMortonStream(BufferStream& tileData, TInt* buffer, std::size_t bufferCount, TTarget* out, std::size_t outCount, const MortonEncodedStreamMetadata& metadata) { decodeStream(tileData, buffer, bufferCount, metadata); assert(outCount == 2 * bufferCount); decodeMortonCodes( buffer, bufferCount, out, outCount, metadata.getNumBits(), metadata.getCoordinateShift()); } #pragma endregion #pragma region Private template requires(std::is_integral_v && (std::is_integral_v || std::is_enum_v)) void IntegerDecoder::decodeStream(BufferStream& tileData, TTarget* out, std::size_t outSize, const StreamMetadata& metadata) { using namespace metadata::stream; assert(outSize >= metadata.getNumValues()); switch (metadata.getPhysicalLevelTechnique()) { case PhysicalLevelTechnique::FAST_PFOR: { std::uint32_t* outPtr = nullptr; std::optional> tempBuffer; if constexpr (sizeof(*out) == sizeof(std::uint32_t)) { // Decode directly into the output buffer outPtr = reinterpret_cast(out); } else { // Decode into a 32-bit temprary buffer ... tempBuffer.emplace(getTempBuffer(metadata.getNumValues())); outPtr = tempBuffer->get(); } const auto resultLength = decodeFastPfor( tileData, outPtr, metadata.getNumValues(), metadata.getByteLength()); if constexpr (sizeof(*out) != sizeof(std::uint32_t)) { // ... then extend to 64-bit output (`.mapToLong(i -> i)` in Java) std::transform(outPtr, outPtr + resultLength, out, [](auto x) { return static_cast(x); }); } break; } case PhysicalLevelTechnique::VARINT: util::decoding::decodeVarints(tileData, static_cast(outSize), out); break; default: throw std::runtime_error("Specified physical level technique not yet supported " + std::to_string(std::to_underlying(metadata.getPhysicalLevelTechnique()))); } } template void IntegerDecoder::decodeRLE(const std::vector& values, std::vector& out, const std::uint32_t numRuns) { std::uint32_t outPos = 0; for (std::uint32_t i = 0; i < numRuns; ++i) { const auto run = values[i]; const auto value = values[i + numRuns]; if (outPos + run > out.size()) { throw std::runtime_error("RLE run exceeds output buffer size"); } std::fill(std::next(out.begin(), outPos), std::next(out.begin(), outPos + run), value); } } template void IntegerDecoder::decodeDeltaRLE(const std::vector& values, std::vector& out, const std::uint32_t numRuns) { std::uint32_t outPos = 0; T previousValue = 0; for (std::uint32_t i = 0; i < numRuns; ++i) { const auto run = values[i]; if (outPos + run > out.size()) { throw std::runtime_error("RLE run exceeds output buffer size"); } const auto value = static_cast>(values[i + numRuns]); const auto delta = util::decoding::decodeZigZag(value); for (std::size_t j = 0; j < run; ++j) { out[outPos++] = static_cast(previousValue += delta); } } } template requires(std::is_integral_v> && std::is_integral_v> && sizeof(T) <= sizeof(TTarget)) void IntegerDecoder::decodeZigZagDelta(const T* values, const std::size_t count, TTarget* const out, const std::size_t outCount) noexcept { using namespace util::decoding; assert(count == outCount); std::uint32_t pos = 0; using ST = std::make_signed_t>; ST previousValue = 0; for (const auto zigZagDelta : std::span{values, count}) { const auto delta = static_cast(decodeZigZag(zigZagDelta)); out[pos++] = static_cast(previousValue += delta); } } template requires(std::is_integral_v && (std::is_integral_v || std::is_enum_v)) void IntegerDecoder::decodeMortonCodes(const TDecode* const data, const std::size_t count, TTarget* const out, std::size_t outCount, int numBits, int coordinateShift) noexcept { using namespace util::decoding; assert(outCount == 2 * count); std::uint32_t previousMortonCode = 0; for (std::size_t i = 0, j = 0; i < count; ++i) { auto mortonCode = static_cast(data[i]); if constexpr (delta) { mortonCode += previousMortonCode; }; out[j++] = static_cast(util::MortonCurve::decode(mortonCode, numBits) - coordinateShift); out[j++] = static_cast(util::MortonCurve::decode(mortonCode >> 1, numBits) - coordinateShift); if constexpr (delta) { previousMortonCode = mortonCode; } } } #pragma endregion Private } // namespace mlt::decoder ================================================ FILE: cpp/src/mlt/decode/property.hpp ================================================ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mlt::decoder { class PropertyDecoder { public: PropertyDecoder(IntegerDecoder& intDecoder_, StringDecoder& stringDecoder_) : intDecoder(intDecoder_), stringDecoder(stringDecoder_) {} PropertyVecMap decodePropertyColumn(BufferStream& tileData, const metadata::tileset::Column& column, std::uint32_t numStreams) { using namespace metadata::tileset; if (std::holds_alternative(column.type)) { PropertyVecMap results; results.emplace(column.name, decodeScalarPropertyColumn(tileData, column, numStreams)); return results; } if (numStreams > 1) { return stringDecoder.decodeSharedDictionary(tileData, column, numStreams); } skipColumn(tileData, numStreams); return {}; } protected: void skipColumn(BufferStream& tileData, std::uint32_t numStreams) { using namespace metadata::stream; using namespace util::decoding; for (std::uint32_t i = 0; i < numStreams; ++i) { auto streamMetadata = StreamMetadata::decode(tileData); if (!streamMetadata) { throw std::runtime_error("Failed to decode stream metadata"); } // Skip the stream data tileData.consume(streamMetadata->getByteLength()); } } PresentProperties decodeScalarPropertyColumn(BufferStream& tileData, const metadata::tileset::Column& column, std::uint32_t numStreams) { using namespace metadata; using namespace metadata::stream; using namespace metadata::tileset; using namespace util::decoding; PackedBitset presentStream; std::uint32_t presentValueCount = 0; if (column.nullable) { const auto presentStreamMetadata = StreamMetadata::decode(tileData); presentValueCount = presentStreamMetadata->getNumValues(); rle::decodeBoolean(tileData, presentStream, *presentStreamMetadata, /*consume=*/true); if ((presentValueCount + 7) / 8 != presentStream.size()) { throw std::runtime_error("invalid present stream"); } numStreams -= 1; } const auto scalarColumn = std::get(column.type); if (!scalarColumn.hasPhysicalType()) { throw std::runtime_error("property column ('" + column.name + "') must be scalar"); } const auto scalarType = scalarColumn.getPhysicalType(); // String stream metadata is read by StringDecoder std::unique_ptr streamMetadata; if (scalarType != ScalarType::STRING) { streamMetadata = StreamMetadata::decode(tileData); } if (column.nullable && streamMetadata && presentValueCount < streamMetadata->getNumValues()) { throw std::runtime_error("Unexpected present value column"); } const auto checkBits = [&](const auto& presentBuffer, const auto& propertyBuffer, bool isBoolean = false) { #ifndef NDEBUG if (!presentStream.empty()) { const auto actualProperties = propertyCount(propertyBuffer, isBoolean); const auto presentBits = countSetBits(presentBuffer); if ((isBoolean && actualProperties / 8 != (presentBits + 7) / 8) || (!isBoolean && actualProperties != presentBits)) { throw std::runtime_error("Property count " + std::to_string(actualProperties) + " doesn't match present bits " + std::to_string(presentBits)); } } #endif }; switch (scalarType) { case ScalarType::BOOLEAN: { std::vector byteBuffer; rle::decodeBoolean(tileData, byteBuffer, *streamMetadata, /*consume=*/true); if (streamMetadata->getNumValues() > 0 && (streamMetadata->getNumValues() + 7) / 8 != byteBuffer.size()) { throw std::runtime_error("column data incomplete"); } checkBits(presentStream, byteBuffer, /*isBoolean=*/true); return {scalarType, byteBuffer, presentStream}; } case ScalarType::INT_8: case ScalarType::UINT_8: throw std::runtime_error("8-bit integer type not implemented"); case ScalarType::INT_32: case ScalarType::UINT_32: { const bool isSigned = (scalarType == ScalarType::INT_32); PropertyVec result; if (isSigned) { std::vector intBuffer; intBuffer.reserve(streamMetadata->getNumValues()); intDecoder.decodeIntStream( tileData, intBuffer, *streamMetadata, isSigned); result = {std::move(intBuffer)}; } else { std::vector uintBuffer; uintBuffer.reserve(streamMetadata->getNumValues()); intDecoder.decodeIntStream( tileData, uintBuffer, *streamMetadata, isSigned); result = {std::move(uintBuffer)}; } checkBits(presentStream, result); return {scalarType, std::move(result), std::move(presentStream)}; } case ScalarType::INT_64: { std::vector longBuffer; longBuffer.reserve(streamMetadata->getNumValues()); intDecoder.decodeIntStream( tileData, longBuffer, *streamMetadata, /*isSigned=*/true); PropertyVec result{std::move(longBuffer)}; checkBits(presentStream, result); return {scalarType, std::move(result), std::move(presentStream)}; } case ScalarType::UINT_64: { std::vector longBuffer; longBuffer.reserve(streamMetadata->getNumValues()); intDecoder.decodeIntStream( tileData, longBuffer, *streamMetadata, /*isSigned=*/false); PropertyVec result{std::move(longBuffer)}; checkBits(presentStream, result); return {scalarType, std::move(result), std::move(presentStream)}; } case ScalarType::DOUBLE: { std::vector doubleBuffer; if (streamMetadata->getNumValues() * sizeof(double) == streamMetadata->getByteLength()) { decodeRaw(tileData, doubleBuffer, *streamMetadata, /*consume=*/true); } else { // Compatibility with tilesets encoded before double support was added std::vector floatBuffer; decodeRaw(tileData, floatBuffer, *streamMetadata, /*consume=*/true); doubleBuffer.reserve(streamMetadata->getNumValues()); std::ranges::transform(floatBuffer, std::back_inserter(doubleBuffer), [](float value) { return static_cast(value); }); } PropertyVec result{std::move(doubleBuffer)}; checkBits(presentStream, result); return {scalarType, std::move(result), std::move(presentStream)}; } case ScalarType::FLOAT: { std::vector floatBuffer; decodeRaw(tileData, floatBuffer, *streamMetadata, /*consume=*/true); PropertyVec result{std::move(floatBuffer)}; checkBits(presentStream, result); return {scalarType, std::move(result), std::move(presentStream)}; } case ScalarType::STRING: { auto strings = stringDecoder.decode(tileData, numStreams); if (column.nullable && countSetBits(presentStream) != strings.getStrings().size()) { throw std::runtime_error("String count doesn't match present value count"); } PropertyVec result{std::move(strings)}; checkBits(presentStream, result); return {scalarType, PropertyVec{std::move(result)}, std::move(presentStream)}; } default: throw std::runtime_error("Unknown scalar type: " + std::to_string(std::to_underlying(scalarType))); } } private: IntegerDecoder& intDecoder; StringDecoder& stringDecoder; }; } // namespace mlt::decoder ================================================ FILE: cpp/src/mlt/decode/string.hpp ================================================ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include namespace mlt::decoder { class StringDecoder { public: StringDecoder(IntegerDecoder& intDecoder_) : intDecoder(intDecoder_) {} /* * String column layouts: * -> plain -> present, length, data * -> dictionary -> present, length, dictionary, data * -> fsst dictionary -> symbolTable, symbolLength, dictionary, length, present, data * */ StringDictViews decode(BufferStream& tileData, std::uint32_t numStreams) { using namespace metadata::stream; using namespace util::decoding; std::optional dictType; std::optional lengthType; std::vector dictionaryStream; std::vector symbolStream; std::vector offsetStream; std::vector dictLengthStream; std::vector symbolLengthStream; std::vector views; for (std::uint32_t i = 0; i < numStreams; ++i) { const auto streamMetadata = StreamMetadata::decode(tileData); switch (streamMetadata->getPhysicalStreamType()) { case PhysicalStreamType::OFFSET: intDecoder.decodeIntStream(tileData, offsetStream, *streamMetadata); break; case PhysicalStreamType::LENGTH: { if (!streamMetadata->getLogicalStreamType() || !streamMetadata->getLogicalStreamType()->getLengthType()) { throw std::runtime_error("Length stream missing logical type"); } lengthType = streamMetadata->getLogicalStreamType()->getLengthType(); auto& target = (lengthType == LengthType::DICTIONARY) ? dictLengthStream : symbolLengthStream; intDecoder.decodeIntStream(tileData, target, *streamMetadata); break; } case PhysicalStreamType::DATA: { if (!streamMetadata->getLogicalStreamType() || !streamMetadata->getLogicalStreamType()->getDictionaryType()) { throw std::runtime_error("Data stream missing logical type"); } dictType = streamMetadata->getLogicalStreamType()->getDictionaryType(); auto& target = (dictType == DictionaryType::SINGLE) ? dictionaryStream : symbolStream; decodeRaw(tileData, target, streamMetadata->getByteLength(), /*consume=*/true); break; } default: throw std::runtime_error("Unsupported stream type"); } } if (!dictLengthStream.empty() && !symbolLengthStream.empty()) { auto data = decodeFSST(symbolStream, symbolLengthStream, dictionaryStream, 2 * dictionaryStream.size()); decodeDictionary(dictLengthStream, data, offsetStream, views); return {std::move(data), std::move(views)}; } else if (!offsetStream.empty() && !dictLengthStream.empty()) { decodeDictionary(dictLengthStream, dictionaryStream, offsetStream, views); return {std::move(dictionaryStream), std::move(views)}; } else if (!symbolLengthStream.empty()) { decodePlain(symbolLengthStream, symbolStream, views); return {std::move(symbolStream), std::move(views)}; } else { throw std::runtime_error("Expected streams missing in string decoding"); } } /// Multiple string columns sharing a dictionary PropertyVecMap decodeSharedDictionary(BufferStream& tileData, const metadata::tileset::Column& column, std::uint32_t numStreams) { using namespace metadata::stream; using namespace metadata::tileset::schema; using namespace util::decoding; if (!column.hasComplexType() || !column.getComplexType().hasChildren() || numStreams < 3) { throw std::runtime_error("Expected struct column for shared dictionary decoding"); } std::vector dictionaryLengthStream; auto dictionaryStream = std::make_shared>(); std::vector symbolLengthStream; std::vector symbolTableStream; PropertyVecMap results; bool dictionaryStreamDecoded = false; while (!dictionaryStreamDecoded && numStreams--) { const auto streamMetadata = metadata::stream::StreamMetadata::decode(tileData); switch (streamMetadata->getPhysicalStreamType()) { case PhysicalStreamType::LENGTH: { const bool isDict = streamMetadata->getLogicalStreamType()->getLengthType() == LengthType::DICTIONARY; auto& target = isDict ? dictionaryLengthStream : symbolLengthStream; intDecoder.decodeIntStream(tileData, target, *streamMetadata); break; } case PhysicalStreamType::DATA: { const auto type = streamMetadata->getLogicalStreamType()->getDictionaryType(); const auto isDict = (type == DictionaryType::SINGLE || type == DictionaryType::SHARED); auto& targetStream = isDict ? *dictionaryStream : symbolTableStream; decodeRaw(tileData, targetStream, streamMetadata->getByteLength(), /*consume=*/true); dictionaryStreamDecoded = isDict; break; } default: throw std::runtime_error("Unsupported stream type"); } } std::vector dictionaryViews; if (!symbolLengthStream.empty() && !symbolTableStream.empty() && !dictionaryLengthStream.empty()) { auto data = std::make_shared>( decodeFSST(symbolTableStream, symbolLengthStream, *dictionaryStream, 2 * dictionaryStream->size())); decodeDictionary(dictionaryLengthStream, *data, dictionaryViews); // retain the decoded dictionary data instead of the original raw data dictionaryStream.swap(data); } else if (!dictionaryLengthStream.empty() && !dictionaryStream->empty()) { decodeDictionary(dictionaryLengthStream, *dictionaryStream, dictionaryViews); } else { throw std::runtime_error("Expected streams missing in shared dictionary decoding"); } for (const auto& child : column.getComplexType().children) { [[maybe_unused]] const auto childStreams = decodeVarint(tileData); if (!child.hasScalarType() || child.getScalarType().getPhysicalType() != ScalarType::STRING) { throw std::runtime_error("Currently only string fields are implemented for a struct"); } PackedBitset presentStream; if (child.nullable) { const auto presentStreamMetadata = metadata::stream::StreamMetadata::decode(tileData); const auto presentValueCount = presentStreamMetadata->getNumValues(); rle::decodeBoolean(tileData, presentStream, *presentStreamMetadata, /*consume=*/true); if ((presentValueCount + 7) / 8 != presentStream.size()) { throw std::runtime_error("invalid present stream"); } } const auto dataStreamMetadata = metadata::stream::StreamMetadata::decode(tileData); std::vector dataReferenceStream; intDecoder.decodeIntStream(tileData, dataReferenceStream, *dataStreamMetadata); std::vector propertyValues; propertyValues.reserve(dataReferenceStream.size()); for (std::uint32_t i = 0; i < dataReferenceStream.size(); ++i) { const auto dictIndex = dataReferenceStream[i]; if (dictIndex >= dictionaryViews.size()) { throw std::runtime_error("StringDecoder: dictionaryViews index out of bounds"); } propertyValues.push_back(dictionaryViews[dictIndex]); } results.emplace(column.name + child.name, PresentProperties{child.getScalarType().getPhysicalType(), StringDictViews(dictionaryStream, propertyValues), std::move(presentStream)}); } return results; } static std::vector decodeFSST(const std::vector& symbols, const std::vector& symbolLengths, const std::vector& compressedData, std::size_t decompressedLength) { return decodeFSST(symbols.data(), symbols.size(), symbolLengths.data(), symbolLengths.size(), compressedData.data(), compressedData.size(), decompressedLength); } static std::vector decodeFSST(const std::uint8_t* symbols, const std::size_t symbolCount, const std::uint32_t* symbolLengths, const std::size_t symbolLengthCount, const std::uint8_t* compressedData, const std::size_t compressedDataCount, const std::size_t decompressedLength) { std::vector output; if (decompressedLength > 0) { output.resize(decompressedLength); } std::vector symbolOffsets(symbolLengthCount); for (size_t i = 1; i < symbolLengthCount; i++) { symbolOffsets[i] = symbolOffsets[i - 1] + symbolLengths[i - 1]; } std::size_t idx = 0; for (size_t i = 0; i < compressedDataCount; i++) { const std::uint8_t symbolIndex = compressedData[i]; // 255 is our escape byte -> take the next symbol as it is if (symbolIndex == 255) { if (idx == output.size()) { output.resize(output.size() * 2); } output[idx++] = compressedData[++i]; } else if (symbolIndex < symbolLengthCount) { const auto len = symbolLengths[symbolIndex]; if (idx + len > output.size()) { output.resize((output.size() + len) * 2); } const auto offset = symbolOffsets[symbolIndex]; if (offset >= symbolCount) { throw std::runtime_error("FSST decode: symbol index out of bounds"); } std::memcpy(&output[idx], &symbols[offset], len); idx += len; } else { throw std::runtime_error("FSST decode: invalid symbol index"); } } output.resize(idx); return output; } private: IntegerDecoder& intDecoder; /// Drop the useless codepoint produced when a UTF-16 Byte-Order-Mark is included in the conversion to UTF-8 // https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8 // TODO: Can we force this on the encoding side instead? static std::string_view view(const char* bytes, std::size_t length) { if (length >= 3 && std::equal(bytes, bytes + 3, "\xEF\xBB\xBF")) { bytes += 3; length -= 3; } return {bytes, length}; } static void decodePlain(const std::vector& lengthStream, const std::vector& utf8bytes, std::vector& out) { std::size_t dataOffset = 0; std::size_t lengthOffset = 0; out.reserve(lengthStream.size()); for (std::uint32_t i = 0; i < lengthStream.size(); ++i) { const auto length = lengthStream[lengthOffset++]; const char* bytes = reinterpret_cast(utf8bytes.data() + dataOffset); out.push_back(view(bytes, length)); dataOffset += length; } } static void decodeDictionary(const std::vector& lengthStream, const std::vector& utf8bytes, std::vector& out) { const auto* const utf8Ptr = reinterpret_cast(utf8bytes.data()); std::size_t offset = 0; for (auto length : lengthStream) { out.emplace_back(utf8Ptr + offset, length); offset += length; } } static void decodeDictionary(const std::vector& lengthStream, const std::vector& utf8bytes, const std::vector& offsets, std::vector& out) { const auto* const utf8Ptr = reinterpret_cast(utf8bytes.data()); std::vector dictionary; dictionary.reserve(lengthStream.size()); std::uint32_t dictionaryOffset = 0; for (const auto length : lengthStream) { dictionary.push_back(view(utf8Ptr + dictionaryOffset, length)); dictionaryOffset += length; } out.reserve(offsets.size()); for (std::uint32_t i = 0; i < offsets.size(); ++i) { out.push_back(dictionary[offsets[i]]); } } }; } // namespace mlt::decoder ================================================ FILE: cpp/src/mlt/decoder.cpp ================================================ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mlt { using namespace decoder; using namespace util::decoding; struct Decoder::Impl { Impl(std::unique_ptr&& geometryFactory_, bool supportFastPFOR) : integerDecoder(supportFastPFOR), geometryDecoder(integerDecoder), geometryFactory(std::move(geometryFactory_)) {} Layer parseBasicMVTEquivalent(BufferStream&); std::vector makeFeatures(const std::vector>&, std::vector>&&); IntegerDecoder integerDecoder; StringDecoder stringDecoder{integerDecoder}; PropertyDecoder propertyDecoder{integerDecoder, stringDecoder}; GeometryDecoder geometryDecoder; std::unique_ptr geometryFactory; }; Layer Decoder::Impl::parseBasicMVTEquivalent(BufferStream& tileData) { using metadata::stream::StreamMetadata; using metadata::type_map::Tag0x01; const auto layerMetadata = mlt::metadata::tileset::decodeFeatureTable(tileData); std::vector> ids; std::unique_ptr geometryVector; PropertyVecMap properties; for (const auto& columnMetadata : layerMetadata.columns) { const auto numStreams = Tag0x01::hasStreamCount(columnMetadata) ? decodeVarint(tileData) : 1; if (columnMetadata.isID()) { PackedBitset idPresentBits; std::uint32_t numFeaturesFromPresent = 0; if (columnMetadata.nullable) { const auto presentStreamMetadata = StreamMetadata::decode(tileData); numFeaturesFromPresent = presentStreamMetadata->getNumValues(); rle::decodeBoolean(tileData, idPresentBits, *presentStreamMetadata, /*consume=*/true); } const auto idDataStreamMetadata = StreamMetadata::decode(tileData); std::vector denseIds(idDataStreamMetadata->getNumValues()); if (columnMetadata.getScalarType().hasLongID) { integerDecoder.decodeIntStream(tileData, denseIds, *idDataStreamMetadata); } else { integerDecoder.decodeIntStream(tileData, denseIds, *idDataStreamMetadata); } if (!idPresentBits.empty()) { // Expand dense IDs to sparse optional IDs using the present bitset ids.resize(numFeaturesFromPresent); std::size_t denseIdx = 0; for (std::uint32_t i = 0; i < numFeaturesFromPresent; ++i) { if (testBit(idPresentBits, i)) { ids[i] = denseIds[denseIdx++]; } } } else { ids.resize(denseIds.size()); for (std::size_t i = 0; i < denseIds.size(); ++i) { ids[i] = denseIds[i]; } } } else if (columnMetadata.isGeometry()) { geometryVector = geometryDecoder.decodeGeometryColumn(tileData, columnMetadata, numStreams); } else { // Decode the property column, which may result in multiple properties (e.g. struct), and merge the results auto columnProperties = propertyDecoder.decodePropertyColumn(tileData, columnMetadata, numStreams); std::copy(std::make_move_iterator(columnProperties.begin()), std::make_move_iterator(columnProperties.end()), std::inserter(properties, properties.end())); } } // Check framing, we expect to use all the data in the buffer provided. if (tileData.available() > 0) { throw std::runtime_error(std::to_string(tileData.available()) + " bytes trailing layer " + layerMetadata.name); } return {layerMetadata.name, layerMetadata.extent, std::move(geometryVector), makeFeatures(ids, geometryVector->getGeometries(*geometryFactory)), std::move(properties)}; } std::vector Decoder::Impl::makeFeatures(const std::vector>& ids, std::vector>&& geometries) { const auto featureCount = geometries.size(); if (!ids.empty() && ids.size() != featureCount) { throw std::runtime_error("ID count (" + std::to_string(ids.size()) + ") does not match geometry count (" + std::to_string(featureCount) + ")"); } return util::generateVector(featureCount, [&](const auto i) { const auto id = ids.empty() ? std::nullopt : ids[i]; return Feature{id, std::move(geometries[i]), static_cast(i)}; }); } Decoder::Decoder(bool supportFastPFOR) : Decoder(std::make_unique(), supportFastPFOR) {} Decoder::Decoder(std::unique_ptr&& geometryFactory, bool supportFastPFOR) : impl{std::make_unique(std::move(geometryFactory), supportFastPFOR)} {} Decoder::~Decoder() noexcept = default; MapLibreTile Decoder::decode(BufferStream tileData) { std::vector layers; while (tileData.available()) { const auto layerLength = decodeVarint(tileData); // Create a new BufferStream for this layer and advance past it in the main stream auto layerStream = tileData.getSubStream(0, layerLength); tileData.consume(layerLength); const auto layerTag = decodeVarint(layerStream); if (layerTag == 1) { layers.push_back(impl->parseBasicMVTEquivalent(layerStream)); } else { // Skipping unknown layer } } return {std::move(layers)}; } MapLibreTile Decoder::decode(DataView tileData) { return decode(BufferStream{tileData}); } } // namespace mlt ================================================ FILE: cpp/src/mlt/feature.cpp ================================================ #include #include #include namespace mlt { Feature::Feature(std::optional ident_, std::unique_ptr&& geometry_, std::uint32_t index_) : ident(ident_), index(index_), geometry(std::move(geometry_)) {} Feature::~Feature() noexcept = default; std::optional Feature::getProperty(const std::string& key, const Layer& layer) const { const auto& propertyMap = layer.getProperties(); if (const auto hit = propertyMap.find(key); hit != propertyMap.end()) { return hit->second.getProperty(index); } return std::nullopt; } } // namespace mlt ================================================ FILE: cpp/src/mlt/geometry_vector.cpp ================================================ #include #include #include #include #include namespace mlt::geometry { namespace { constexpr void checkBuffer(std::size_t index, std::size_t size, std::string_view name) { if (index >= size) { throw std::runtime_error(std::string(name) + " underflow"); } } #define CHECK_BUFFER(I, B) checkBuffer(I, B.size(), #B) inline Coordinate coord(std::int32_t x, std::int32_t y) { return {static_cast(x), static_cast(y)}; } std::vector getLineStringCoords(const std::vector& vertexBuffer, std::uint32_t startIndex, const std::uint32_t numVertices, bool closeLineString) { std::vector coords; coords.reserve(numVertices + 1); CHECK_BUFFER(startIndex + numVertices - 1, vertexBuffer); for (std::uint32_t i = 0; i < numVertices; ++i) { const auto x = vertexBuffer[startIndex++]; coords.push_back(coord(x, vertexBuffer[startIndex++])); } if (closeLineString && !coords.empty() && coords.front() != coords.back()) { coords.push_back(coords.front()); } return coords; } std::vector getDictionaryEncodedLineStringCoords(const std::vector& vertexBuffer, const std::vector& vertexOffsets, std::uint32_t vertexOffset, const std::uint32_t numVertices, const bool closeLineString) { std::vector coords; coords.reserve(numVertices + 1); CHECK_BUFFER(vertexOffset + numVertices - 1, vertexOffsets); for (std::uint32_t i = 0; i < numVertices; ++i) { const auto offset = 2 * vertexOffsets[vertexOffset++]; CHECK_BUFFER(offset + 1, vertexBuffer); coords.push_back(coord(vertexBuffer[offset], vertexBuffer[offset + 1])); } if (closeLineString && !coords.empty() && coords.front() != coords.back()) { coords.push_back(coords.front()); } return coords; } std::vector getMortonEncodedLineStringCoords(const std::vector& vertexBuffer, const std::vector& vertexOffsets, std::uint32_t vertexOffset, const std::uint32_t numVertices, const MortonSettings& mortonSettings, const bool closeLineString) { std::vector coords; coords.reserve(numVertices + 1); CHECK_BUFFER(vertexOffset + numVertices - 1, vertexOffsets); for (std::uint32_t i = 0; i < numVertices; ++i) { const auto offset = vertexOffsets[vertexOffset++]; CHECK_BUFFER(offset, vertexBuffer); coords.push_back( util::MortonCurve::decode(vertexBuffer[offset], mortonSettings.numBits, mortonSettings.coordinateShift)); } if (closeLineString && !coords.empty() && coords.front() != coords.back()) { coords.push_back(coords.front()); } return coords; } } // namespace void GeometryVector::applyTriangles(Geometry& geom, std::uint32_t& triangleOffset, std::uint32_t& indexBufferOffset, [[maybe_unused]] std::uint32_t totalVertices, [[maybe_unused]] bool multiPolygon) const { if (triangleCounts.empty()) { return; } CHECK_BUFFER(triangleOffset, triangleCounts); const auto numTriangles = triangleCounts[triangleOffset++]; if (numTriangles) { CHECK_BUFFER(indexBufferOffset + (3 * numTriangles) - 1, indexBuffer); assert(std::all_of(std::next(indexBuffer.cbegin(), indexBufferOffset), std::next(indexBuffer.cbegin(), indexBufferOffset + (3 * numTriangles)), [=](auto i) { return i < totalVertices; })); #if !defined(NDEBUG) && false // Expect the tessellated indexes to reference the entire range of vertices. // The Java implementation of Earcut makes this fail, so it's disabled for now. const auto limits = std::ranges::minmax_element(&indexBuffer[indexBufferOffset], &indexBuffer[indexBufferOffset + (3 * numTriangles)]); assert(*limits.min == 0 && *limits.max == totalVertices - 1); #endif geom.setTriangles({&indexBuffer[indexBufferOffset], 3 * numTriangles}); indexBufferOffset += 3 * numTriangles; } } std::vector> GeometryVector::getGeometries(const GeometryFactory& factory) const { std::vector> geometries; geometries.reserve(numGeometries); std::uint32_t partOffsetCounter = 1; std::uint32_t ringOffsetsCounter = 1; std::uint32_t geometryOffsetsCounter = 1; std::uint32_t vertexBufferOffset = 0; std::uint32_t vertexOffsetsOffset = 0; std::uint32_t indexBufferOffset = 0; std::uint32_t triangleOffset = 0; const auto containsPolygon = containsPolygonGeometry(); for (std::size_t i = 0; i < numGeometries; ++i) { using metadata::tileset::GeometryType; const auto geomType = getGeometryType(i); switch (geomType) { case GeometryType::POINT: if (vertexOffsets.empty()) { CHECK_BUFFER(vertexBufferOffset + 1, vertexBuffer); geometries.push_back( factory.createPoint({static_cast(vertexBuffer[vertexBufferOffset]), static_cast(vertexBuffer[vertexBufferOffset + 1])})); vertexBufferOffset += 2; } else if (vertexBufferType == VertexBufferType::VEC_2) { CHECK_BUFFER(vertexOffsetsOffset, vertexOffsets); const auto vertexOffset = vertexOffsets[vertexOffsetsOffset++] * 2; CHECK_BUFFER(vertexOffset + 1, vertexBuffer); geometries.push_back(factory.createPoint({static_cast(vertexBuffer[vertexOffset]), static_cast(vertexBuffer[vertexOffset + 1])})); } else { assert(vertexBufferType == VertexBufferType::MORTON); CHECK_BUFFER(vertexOffsetsOffset, vertexOffsets); const auto mortonCode = vertexOffsets[vertexOffsetsOffset++]; geometries.push_back(factory.createPoint(util::MortonCurve::decode( mortonCode, mortonSettings->numBits, mortonSettings->coordinateShift))); } geometryOffsetsCounter++; partOffsetCounter++; ringOffsetsCounter++; break; case GeometryType::MULTIPOINT: { if (!topologyVector) { throw std::runtime_error("Multi-point geometry without topology vector"); } const auto& geometryOffsets = topologyVector->getGeometryOffsets(); [[maybe_unused]] const auto& partOffsets = topologyVector->getPartOffsets(); [[maybe_unused]] const auto& ringOffsets = topologyVector->getRingOffsets(); CHECK_BUFFER(geometryOffsetsCounter, geometryOffsets); const auto numPoints = geometryOffsets[geometryOffsetsCounter] - geometryOffsets[geometryOffsetsCounter - 1]; geometryOffsetsCounter++; if (numPoints) { std::vector vertices; vertices.reserve(numPoints); if (vertexOffsets.empty()) { CHECK_BUFFER(vertexBufferOffset + (2 * numPoints) - 1, vertexBuffer); for (std::uint32_t j = 0; j < numPoints; ++j) { const auto x = vertexBuffer[vertexBufferOffset++]; vertices.push_back(coord(x, vertexBuffer[vertexBufferOffset++])); } } else { CHECK_BUFFER(vertexOffsetsOffset + numPoints - 1, vertexOffsets); for (std::uint32_t j = 0; j < numPoints; ++j) { const auto offset = vertexOffsets[vertexOffsetsOffset++] * 2; vertices.push_back(coord(vertexBuffer[offset], vertexBuffer[offset + 1])); } } geometries.push_back(factory.createMultiPoint(std::move(vertices))); } partOffsetCounter += numPoints; ringOffsetsCounter += numPoints; break; } case GeometryType::LINESTRING: { if (!topologyVector) { throw std::runtime_error("Linestring geometry without topology vector"); } std::uint32_t numVertices = 0; if (containsPolygon) { const auto& ringOffsets = topologyVector->getRingOffsets(); CHECK_BUFFER(ringOffsetsCounter, ringOffsets); numVertices = ringOffsets[ringOffsetsCounter] - ringOffsets[ringOffsetsCounter - 1]; ringOffsetsCounter++; } else { const auto& partOffsets = topologyVector->getPartOffsets(); CHECK_BUFFER(partOffsetCounter, partOffsets); numVertices = partOffsets[partOffsetCounter] - partOffsets[partOffsetCounter - 1]; } partOffsetCounter++; std::vector vertices; if (vertexOffsets.empty()) { vertices = getLineStringCoords( vertexBuffer, vertexBufferOffset, numVertices, /*closeLineString=*/false); vertexBufferOffset += numVertices * 2; } else { vertices = (vertexBufferType == VertexBufferType::VEC_2) ? getDictionaryEncodedLineStringCoords(vertexBuffer, vertexOffsets, vertexOffsetsOffset, numVertices, /*closeLineString=*/false) : getMortonEncodedLineStringCoords(vertexBuffer, vertexOffsets, vertexOffsetsOffset, numVertices, *mortonSettings, /*closeLineString=*/false); vertexOffsetsOffset += numVertices; } geometryOffsetsCounter++; geometries.push_back(factory.createLineString(std::move(vertices))); break; } case GeometryType::POLYGON: { if (!topologyVector) { throw std::runtime_error("Polygon geometry without topology vector"); } [[maybe_unused]] const auto& geometryOffsets = topologyVector->getGeometryOffsets(); const auto& partOffsets = topologyVector->getPartOffsets(); const auto& ringOffsets = topologyVector->getRingOffsets(); CHECK_BUFFER(partOffsetCounter, partOffsets); const auto numRings = partOffsets[partOffsetCounter] - partOffsets[partOffsetCounter - 1]; partOffsetCounter++; CHECK_BUFFER(ringOffsetsCounter, ringOffsets); const auto numVertices = ringOffsets[ringOffsetsCounter] - ringOffsets[ringOffsetsCounter - 1]; ringOffsetsCounter++; std::vector rings; rings.reserve(numRings); auto totalVertices = numVertices; if (vertexOffsets.empty()) { rings.push_back( getLineStringCoords(vertexBuffer, vertexBufferOffset, numVertices, /*closeLineString=*/true)); vertexBufferOffset += numVertices * 2; assert(triangleCounts.empty() || numVertices == rings.back().size() || numVertices == rings.back().size() - 1); for (std::uint32_t j = 1; j < numRings; ++j) { CHECK_BUFFER(ringOffsetsCounter, ringOffsets); const auto numRingVertices = ringOffsets[ringOffsetsCounter] - ringOffsets[ringOffsetsCounter - 1]; totalVertices += numRingVertices; ringOffsetsCounter++; const auto vbo = vertexBufferOffset; vertexBufferOffset += numRingVertices * 2; rings.push_back( getLineStringCoords(vertexBuffer, vbo, numRingVertices, /*closeLineString=*/true)); assert(triangleCounts.empty() || numRingVertices == rings.back().size() || numRingVertices == rings.back().size() - 1); } auto newGeometry = factory.createPolygon(std::move(rings)); applyTriangles(*newGeometry, triangleOffset, indexBufferOffset, totalVertices, false); geometries.push_back(std::move(newGeometry)); } else { rings.push_back((vertexBufferType == VertexBufferType::VEC_2) ? getDictionaryEncodedLineStringCoords(vertexBuffer, vertexOffsets, vertexOffsetsOffset, numVertices, /*closeLineString=*/true) : getMortonEncodedLineStringCoords(vertexBuffer, vertexOffsets, vertexOffsetsOffset, numVertices, *mortonSettings, /*closeLineString=*/true)); vertexOffsetsOffset += numVertices; assert(triangleCounts.empty() || numVertices == rings.back().size() || numVertices == rings.back().size() - 1); auto dictTotalVertices = numVertices; for (std::uint32_t j = 1; j < numRings; ++j) { CHECK_BUFFER(ringOffsetsCounter, ringOffsets); const auto numRingVertices = ringOffsets[ringOffsetsCounter] - ringOffsets[ringOffsetsCounter - 1]; ringOffsetsCounter++; dictTotalVertices += numRingVertices; const auto offset = vertexOffsetsOffset; vertexOffsetsOffset += numRingVertices; rings.push_back( (vertexBufferType == VertexBufferType::VEC_2) ? getDictionaryEncodedLineStringCoords( vertexBuffer, vertexOffsets, offset, numRingVertices, /*closeLineString=*/true) : getMortonEncodedLineStringCoords(vertexBuffer, vertexOffsets, offset, numRingVertices, *mortonSettings, /*closeLineString=*/true)); assert(triangleCounts.empty() || numRingVertices == rings.back().size()); } auto newGeometry = factory.createPolygon(std::move(rings)); applyTriangles(*newGeometry, triangleOffset, indexBufferOffset, dictTotalVertices, false); geometries.push_back(std::move(newGeometry)); } if (topologyVector && !topologyVector->getGeometryOffsets().empty()) { geometryOffsetsCounter++; } break; } case GeometryType::MULTILINESTRING: { if (!topologyVector) { throw std::runtime_error("Multi-Linestring geometry without topology vector"); } const auto& geometryOffsets = topologyVector->getGeometryOffsets(); const auto& partOffsets = topologyVector->getPartOffsets(); const auto& ringOffsets = topologyVector->getRingOffsets(); CHECK_BUFFER(geometryOffsetsCounter, geometryOffsets); const auto numLineStrings = geometryOffsets[geometryOffsetsCounter] - geometryOffsets[geometryOffsetsCounter - 1]; geometryOffsetsCounter++; std::vector lineStrings; lineStrings.reserve(numLineStrings); if (vertexOffsets.empty()) { for (std::uint32_t j = 0; j < numLineStrings; j++) { std::uint32_t numVertices = 0; if (containsPolygon) { CHECK_BUFFER(ringOffsetsCounter, ringOffsets); numVertices = ringOffsets[ringOffsetsCounter] - ringOffsets[ringOffsetsCounter - 1]; ringOffsetsCounter++; } else { CHECK_BUFFER(partOffsetCounter, partOffsets); numVertices = partOffsets[partOffsetCounter] - partOffsets[partOffsetCounter - 1]; } partOffsetCounter++; lineStrings.push_back(getLineStringCoords( vertexBuffer, vertexBufferOffset, numVertices, /*closeLineString=*/false)); vertexBufferOffset += numVertices * 2; } geometries.push_back(factory.createMultiLineString(std::move(lineStrings))); } else { for (std::uint32_t j = 0; j < numLineStrings; ++j) { std::uint32_t numVertices = 0; if (containsPolygon) { CHECK_BUFFER(ringOffsetsCounter, ringOffsets); numVertices = ringOffsets[ringOffsetsCounter] - ringOffsets[ringOffsetsCounter - 1]; ringOffsetsCounter++; } else { CHECK_BUFFER(partOffsetCounter, partOffsets); numVertices = partOffsets[partOffsetCounter] - partOffsets[partOffsetCounter - 1]; } partOffsetCounter++; lineStrings.push_back((vertexBufferType == VertexBufferType::VEC_2) ? getDictionaryEncodedLineStringCoords(vertexBuffer, vertexOffsets, vertexOffsetsOffset, numVertices, /*closeLineString=*/false) : getMortonEncodedLineStringCoords(vertexBuffer, vertexOffsets, vertexOffsetsOffset, numVertices, *mortonSettings, /*closeLineString=*/false)); vertexOffsetsOffset += numVertices; } geometries.push_back(factory.createMultiLineString(std::move(lineStrings))); } break; } case GeometryType::MULTIPOLYGON: { if (!topologyVector) { throw std::runtime_error("Multi-polygon geometry without topology vector"); } const auto& geometryOffsets = topologyVector->getGeometryOffsets(); const auto& partOffsets = topologyVector->getPartOffsets(); const auto& ringOffsets = topologyVector->getRingOffsets(); CHECK_BUFFER(geometryOffsetsCounter, geometryOffsets); const auto numPolygons = geometryOffsets[geometryOffsetsCounter] - geometryOffsets[geometryOffsetsCounter - 1]; geometryOffsetsCounter++; std::vector polygons; polygons.reserve(numPolygons); if (vertexOffsets.empty()) { [[maybe_unused]] std::uint32_t plainTotalVertices = 0; for (std::uint32_t j = 0; j < numPolygons; ++j) { CHECK_BUFFER(partOffsetCounter, partOffsets); const auto numRings = partOffsets[partOffsetCounter] - partOffsets[partOffsetCounter - 1]; partOffsetCounter++; CHECK_BUFFER(ringOffsetsCounter, ringOffsets); const auto numVertices = ringOffsets[ringOffsetsCounter] - ringOffsets[ringOffsetsCounter - 1]; ringOffsetsCounter++; plainTotalVertices += numVertices; std::vector rings; rings.reserve(numRings); rings.push_back(getLineStringCoords( vertexBuffer, vertexBufferOffset, numVertices, /*closeLineString=*/true)); vertexBufferOffset += numVertices * 2; for (std::uint32_t k = 1; k < numRings; ++k) { CHECK_BUFFER(ringOffsetsCounter, ringOffsets); const auto numRingVertices = ringOffsets[ringOffsetsCounter] - ringOffsets[ringOffsetsCounter - 1]; ringOffsetsCounter++; plainTotalVertices += numRingVertices; rings.push_back(getLineStringCoords( vertexBuffer, vertexBufferOffset, numRingVertices, /*closeLineString=*/true)); vertexBufferOffset += numRingVertices * 2; } polygons.push_back(std::move(rings)); } auto newGeometry = factory.createMultiPolygon(std::move(polygons)); applyTriangles(*newGeometry, triangleOffset, indexBufferOffset, plainTotalVertices, true); geometries.push_back(std::move(newGeometry)); } else { [[maybe_unused]] std::uint32_t dictTotalVertices = 0; for (std::uint32_t j = 0; j < numPolygons; ++j) { CHECK_BUFFER(partOffsetCounter, partOffsets); const auto numRings = partOffsets[partOffsetCounter] - partOffsets[partOffsetCounter - 1]; partOffsetCounter++; CHECK_BUFFER(ringOffsetsCounter, ringOffsets); const auto numVertices = ringOffsets[ringOffsetsCounter] - ringOffsets[ringOffsetsCounter - 1]; ringOffsetsCounter++; dictTotalVertices += numVertices; std::vector rings; rings.reserve(numRings); rings.push_back((vertexBufferType == VertexBufferType::VEC_2) ? getDictionaryEncodedLineStringCoords(vertexBuffer, vertexOffsets, vertexOffsetsOffset, numVertices, /*closeLineString=*/true) : getMortonEncodedLineStringCoords(vertexBuffer, vertexOffsets, vertexOffsetsOffset, numVertices, *mortonSettings, /*closeLineString=*/true)); vertexOffsetsOffset += numVertices; for (std::uint32_t k = 1; k < numRings; ++k) { CHECK_BUFFER(ringOffsetsCounter, ringOffsets); const auto numRingVertices = ringOffsets[ringOffsetsCounter] - ringOffsets[ringOffsetsCounter - 1]; ringOffsetsCounter++; dictTotalVertices += numRingVertices; rings.push_back((vertexBufferType == VertexBufferType::VEC_2) ? getDictionaryEncodedLineStringCoords(vertexBuffer, vertexOffsets, vertexOffsetsOffset, numRingVertices, /*closeLineString=*/true) : getMortonEncodedLineStringCoords(vertexBuffer, vertexOffsets, vertexOffsetsOffset, numRingVertices, *mortonSettings, /*closeLineString=*/true)); vertexOffsetsOffset += numRingVertices; } polygons.push_back(std::move(rings)); } auto newGeometry = factory.createMultiPolygon(std::move(polygons)); applyTriangles(*newGeometry, triangleOffset, indexBufferOffset, dictTotalVertices, true); geometries.push_back(std::move(newGeometry)); } break; } default: throw std::runtime_error("Unsupported geometry type: " + std::to_string(std::to_underlying(geomType))); } } // If we didn't use all the input data, that's a bug. assert(indexBufferOffset == indexBuffer.size()); assert(triangleOffset == triangleCounts.size()); // TODO: this fails for `test/expected/amazon_here/8_132_85.mlt` // assert(!topologyVector || topologyVector->getPartOffsets().empty() || // partOffsetCounter == topologyVector->getPartOffsets().size()); assert(!topologyVector || topologyVector->getRingOffsets().empty() || ringOffsetsCounter == topologyVector->getRingOffsets().size()); assert(!topologyVector || topologyVector->getGeometryOffsets().empty() || geometryOffsetsCounter == topologyVector->getGeometryOffsets().size()); // If we're using vertex offsets, we should have used all // of them, and we won't have used vertex buffer offsets. assert(!vertexOffsets.empty() || vertexBufferOffset == vertexBuffer.size()); assert(vertexOffsets.empty() || vertexOffsetsOffset == vertexOffsets.size()); return geometries; } } // namespace mlt::geometry ================================================ FILE: cpp/src/mlt/layer.cpp ================================================ #include #include namespace mlt { Layer::Layer(std::string name_, extent_t extent_, std::unique_ptr&& geometryVector_, std::vector features_, PropertyVecMap properties_) noexcept : name(std::move(name_)), extent(extent_), geometryVector(std::move(geometryVector_)), features(std::move(features_)), properties(std::move(properties_)) {} Layer::~Layer() = default; } // namespace mlt ================================================ FILE: cpp/src/mlt/metadata/stream.cpp ================================================ #include #include #include namespace mlt::metadata::stream { namespace { std::optional decodeLogicalStreamType(PhysicalStreamType physicalStreamType, int value) { switch (physicalStreamType) { case PhysicalStreamType::DATA: { const auto type = static_cast(value); if (type < DictionaryType::VALUE_COUNT) { return type; } break; } case PhysicalStreamType::OFFSET: { const auto type = static_cast(value); if (type < OffsetType::VALUE_COUNT) { return type; } break; } case PhysicalStreamType::LENGTH: { const auto type = static_cast(value); if (type < LengthType::VALUE_COUNT) { return type; } break; } case PhysicalStreamType::PRESENT: return {}; default: break; } throw std::runtime_error("Invalid logical stream type: " + std::to_string(std::to_underlying(physicalStreamType))); } } // namespace std::unique_ptr StreamMetadata::decode(BufferStream& tileData) { auto streamMetadata = decodeInternal(tileData); // Currently Morton can't be combined with RLE only with delta if (streamMetadata.getLogicalLevelTechnique1() == LogicalLevelTechnique::MORTON) { auto result = MortonEncodedStreamMetadata::decodePartial(std::move(streamMetadata), tileData); return std::make_unique(std::move(result)); } // Boolean RLE doesn't need additional information else if ((streamMetadata.getLogicalLevelTechnique1() == LogicalLevelTechnique::RLE || streamMetadata.getLogicalLevelTechnique2() == LogicalLevelTechnique::RLE) && streamMetadata.getPhysicalLevelTechnique() != PhysicalLevelTechnique::NONE) { auto result = RleEncodedStreamMetadata::decodePartial(std::move(streamMetadata), tileData); return std::make_unique(std::move(result)); } return std::make_unique(std::move(streamMetadata)); } int StreamMetadata::getLogicalType() const noexcept { if (logicalStreamType) { if (logicalStreamType->getDictionaryType()) { return std::to_underlying(*logicalStreamType->getDictionaryType()); } if (logicalStreamType->getLengthType()) { return std::to_underlying(*logicalStreamType->getLengthType()); } if (logicalStreamType->getOffsetType()) { return std::to_underlying(*logicalStreamType->getOffsetType()); } } return 0; } StreamMetadata StreamMetadata::decodeInternal(BufferStream& tileData) { const auto streamType = tileData.read(); const auto physicalStreamType = static_cast(streamType >> 4); auto logicalStreamType = decodeLogicalStreamType(physicalStreamType, streamType & 0x0f); const auto encodingsHeader = tileData.read() & 0xff; const auto logicalLevelTechnique1 = static_cast(encodingsHeader >> 5); const auto logicalLevelTechnique2 = static_cast((encodingsHeader >> 2) & 0x7); const auto physicalLevelTechnique = static_cast(encodingsHeader & 0x3); if (physicalStreamType >= PhysicalStreamType::VALUE_COUNT || logicalLevelTechnique1 >= LogicalLevelTechnique::VALUE_COUNT || logicalLevelTechnique2 >= LogicalLevelTechnique::VALUE_COUNT || physicalLevelTechnique >= PhysicalLevelTechnique::VALUE_COUNT) { throw std::runtime_error("Invalid stream encoding"); } using namespace util::decoding; const auto [numValues, byteLength] = decodeVarints(tileData); return { physicalStreamType, std::move(logicalStreamType), logicalLevelTechnique1, logicalLevelTechnique2, physicalLevelTechnique, numValues, byteLength, }; } } // namespace mlt::metadata::stream ================================================ FILE: cpp/src/mlt/metadata/tileset.cpp ================================================ #include #include #include #include #include #include namespace mlt::metadata::tileset { using util::decoding::decodeVarint; namespace { std::string decodeString(BufferStream& tileData) { std::string result; const auto length = decodeVarint(tileData); if (length > 0) { result.resize(length); tileData.read(result.data(), length); } return result; } Column decodeColumn(BufferStream& tileData) { const auto typeCode = decodeVarint(tileData); auto column = type_map::Tag0x01::decodeColumnType(typeCode); if (!column) { // We can't just skip this because we don't know the actual length throw std::runtime_error("Unsupported column type code: " + std::to_string(typeCode)); } if (type_map::Tag0x01::columnTypeHasName(typeCode)) { column->name = decodeString(tileData); } if (type_map::Tag0x01::columnTypeHasChildren(typeCode)) { assert(column->hasComplexType()); auto& complex = column->getComplexType(); const auto childCount = decodeVarint(tileData); complex.children = util::generateVector(childCount, [&](auto) { return decodeColumn(tileData); }); } return *column; } } // namespace FeatureTable decodeFeatureTable(BufferStream& tileData) { auto name = decodeString(tileData); const auto extent = decodeVarint(tileData); const auto columnCount = decodeVarint(tileData); auto columns = util::generateVector(columnCount, [&](auto) { return decodeColumn(tileData); }); return {.name = std::move(name), .extent = extent, .columns = std::move(columns)}; } } // namespace mlt::metadata::tileset ================================================ FILE: cpp/src/mlt/properties.cpp ================================================ #include #include #include #include #include #include #include namespace mlt { namespace { // Visitor to extract a single `Property` from each type of `PropertyVec` struct ExtractPropertyVisitor { ExtractPropertyVisitor(std::size_t i_, bool byteIsBooleans_) : i(i_), byteIsBooleans(byteIsBooleans_) {} template std::optional operator()(const T& vec) const; template std::optional operator()(const std::vector& vec) const { assert(i < vec.size()); return (i < vec.size()) ? std::optional{vec[i]} : std::nullopt; } std::optional operator()(const std::vector& vec) const { if (byteIsBooleans) { assert(i < 8 * vec.size()); return testBit(vec, i); } assert(i < vec.size()); return (i < vec.size()) ? std::optional{static_cast(vec[i])} : std::nullopt; } private: const std::size_t i; const bool byteIsBooleans; }; template <> std::optional ExtractPropertyVisitor::operator()(const StringDictViews& views) const { const auto& strings = views.getStrings(); assert(i < strings.size()); return (i < strings.size()) ? std::optional{strings[i]} : std::nullopt; } auto getPropertyValue(const PropertyVec& layerProperties, std::size_t sourceIndex, bool isBoolean) { const auto value = std::visit(ExtractPropertyVisitor(sourceIndex, isBoolean), layerProperties); if (value) { return *value; } else { throw std::runtime_error("Missing property value"); } }; template std::vector buildIndexVector(const PackedBitset& present) { std::vector indexes; indexes.reserve(8 * present.size()); T curPhysicalIndex = 0; constexpr auto nullIndex = std::numeric_limits::max(); for (const auto bits : present) { for (std::uint8_t i = 0; i < 8; ++i) { indexes.push_back((bits & (1 << i)) ? curPhysicalIndex++ : nullIndex); } } return indexes; } } // namespace PresentProperties::PresentProperties(ScalarType type_, PropertyVec properties_, const PackedBitset& present) noexcept : type(type_), properties(std::move(properties_)) { if (!present.empty()) { if (8 * present.size() < std::numeric_limits::max()) { physicalIndexes = buildIndexVector(present); } else { physicalIndexes = buildIndexVector(present); } } } std::optional PresentProperties::getProperty(std::uint32_t logicalIndex) const { using OptIndex = std::optional; const auto index = std::visit( util::overloaded{[&](const std::monostate&) -> OptIndex { // not an optional property, physical index is the same as // logical index return logicalIndex; }, [&](const auto& indexes) { if (logicalIndex < indexes.size()) { const auto idx = indexes[logicalIndex]; return (idx < std::numeric_limits::max()) ? OptIndex{idx} : OptIndex{}; } return OptIndex{}; }}, physicalIndexes); return index ? getPropertyValue(properties, *index, isBoolean()) : std::optional{}; } } // namespace mlt ================================================ FILE: cpp/src/mlt/util/json_diff.hpp ================================================ #pragma once #include #if MLT_WITH_JSON #include #include namespace mlt::util { using json = nlohmann::json; // Comparison functor that can compare stringified numbers and consider precision struct JSONComparer { const double doubleEpsilon = 1.0e-15; bool operator()(const json& left, const json& right) const { const auto leftDouble = getDouble(left); const auto rightDouble = getDouble(right); // Very similar numbers are equivalent, even if one or both are in string form if (leftDouble && rightDouble) { const auto md = (*leftDouble + *rightDouble) / 2; return (std::fabs(*leftDouble - *rightDouble) / ((md == 0) ? 1 : md)) < doubleEpsilon; } // Missing entries are equivalent to numeric zeros if ((left.is_null() && rightDouble == 0) || (right.is_null() && leftDouble == 0)) { return true; } // Empty objects and arrays are equivalent to missing entries if (((left.is_object() || left.is_array()) && left.empty() && right.is_null()) || (left.is_null() && (right.is_object() || right.is_array()) && right.empty())) { return true; } return (left == right); } private: static std::optional getDouble(const json& v) { switch (v.type()) { case nlohmann::json_abi_v3_11_3::detail::value_t::null: return 0; case nlohmann::json_abi_v3_11_3::detail::value_t::number_integer: case nlohmann::json_abi_v3_11_3::detail::value_t::number_unsigned: case nlohmann::json_abi_v3_11_3::detail::value_t::number_float: return v.get(); case nlohmann::json_abi_v3_11_3::detail::value_t::string: { const auto s = v.get(); char* end = nullptr; const auto d = std::strtof(s.c_str(), &end); if (end - s.c_str() == static_cast(s.size())) { return d; } return {}; // not a number } default: return {}; } } }; /// Based on `nlohmann:json::diff` but with a custom comparator and puts the old/expected value in the result static json diff(const json& source, const json& target, const std::string& path = {}, std::function compare = JSONComparer()) { using namespace nlohmann; using nlohmann::detail::concat; using nlohmann::detail::escape; // the patch json result(json::value_t::array); // if the values are the same, return empty patch if (compare(source, target)) { return result; } if (source.type() != target.type()) { // different types: replace value result.push_back({{"op", "replace"}, {"path", path}, {"value", target}, {"original", source}}); return result; } switch (source.type()) { case json::value_t::array: { // first pass: traverse common elements std::size_t i = 0; while (i < source.size() && i < target.size()) { // recursive call to compare array values at index i auto temp_diff = diff(source[i], target[i], concat(path, '/', std::to_string(i))); result.insert(result.end(), temp_diff.begin(), temp_diff.end()); ++i; } // We now reached the end of at least one array // in a second pass, traverse the remaining elements // remove my remaining elements const auto end_index = static_cast(result.size()); while (i < source.size()) { // add operations in reverse order to avoid invalid // indices result.insert( result.begin() + end_index, json::object( {{"op", "remove"}, {"path", concat(path, '/', std::to_string(i))}, {"value", source[i]}})); ++i; } // add other remaining elements while (i < target.size()) { result.push_back({{"op", "add"}, {"path", concat(path, "/-")}, {"value", target[i]}}); ++i; } break; } case json::value_t::object: { // first pass: traverse this object's elements for (auto it = source.cbegin(); it != source.cend(); ++it) { // escape the key name to be used in a JSON patch const auto path_key = concat(path, '/', escape(it.key())); if (target.find(it.key()) != target.end()) { // recursive call to compare object values at key it auto temp_diff = diff(it.value(), target[it.key()], path_key); result.insert(result.end(), temp_diff.begin(), temp_diff.end()); } else { // found a key that is not in o. // If the value is equivalent to nothing, that's not a difference. const auto& sourceValue = source[it.key()]; if (!compare(sourceValue, json())) { result.push_back(json::object({{"op", "remove"}, {"path", path_key}, {"value", sourceValue}})); } } } // second pass: traverse other object's elements for (auto it = target.cbegin(); it != target.cend(); ++it) { if (source.find(it.key()) == source.end()) { // found a key that is not in this // If the value is equivalent to nothing, that's not a difference. if (!compare(json(), *it)) { const auto path_key = concat(path, '/', escape(it.key())); result.push_back({{"op", "add"}, {"path", path_key}, {"value", *it}}); } } } break; } case json::value_t::null: case json::value_t::string: case json::value_t::boolean: case json::value_t::number_integer: case json::value_t::number_unsigned: case json::value_t::number_float: case json::value_t::binary: case json::value_t::discarded: default: { // both primitive type: replace value result.push_back({{"op", "replace"}, {"path", path}, {"value", target}, {"original", source}}); break; } } return result; } } // namespace mlt::util #endif // MLT_WITH_JSON ================================================ FILE: cpp/src/mlt/util/morton_curve.hpp ================================================ #pragma once #include namespace mlt::util { class MortonCurve : public SpaceFillingCurve { public: MortonCurve(std::int32_t minVertexValue, std::int32_t maxVertexValue) : SpaceFillingCurve(minVertexValue, maxVertexValue) {} std::uint32_t encode(const Coordinate& vertex) const override { validate(vertex); return encodeMorton(vertex, numBits, coordinateShift); } Coordinate decode(std::uint32_t mortonCode) const noexcept override { return decode(mortonCode, numBits, coordinateShift); } static Coordinate decode(std::uint32_t mortonCode, std::uint32_t numBits, std::int32_t coordinateShift) noexcept { return {static_cast(decode(mortonCode, numBits) - coordinateShift), static_cast(decode(mortonCode >> 1, numBits) - coordinateShift)}; } static std::int32_t decode(std::uint32_t code, std::uint32_t numBits) noexcept { std::uint32_t coordinate = 0; for (std::uint32_t i = 0; i < numBits; ++i) { coordinate |= (code & (1ul << (2 * i))) >> i; } return static_cast(coordinate); } private: static std::uint32_t encodeMorton(const Coordinate& vertex, std::uint32_t numBits, std::int32_t coordinateShift) noexcept { const auto shiftedX = static_cast(vertex.x + coordinateShift); const auto shiftedY = static_cast(vertex.y + coordinateShift); std::uint32_t mortonCode = 0; for (std::uint32_t i = 0; i < numBits; ++i) { const auto mask = 1u << i; mortonCode |= ((shiftedX & mask) << i) | ((shiftedY & mask) << (i + 1)); } return mortonCode; } }; } // namespace mlt::util ================================================ FILE: cpp/src/mlt/util/raw.hpp ================================================ #pragma once #include #include #include #include namespace mlt::util::decoding { inline void decodeRaw(BufferStream& tileData, std::vector& out, std::uint32_t numBytes, bool consume) { out.resize(numBytes); std::copy(tileData.getReadPosition(), tileData.getReadPosition() + numBytes, out.begin()); if (consume) { tileData.consume(numBytes); } } template void decodeRaw(BufferStream& tileData, std::vector& out, const metadata::stream::StreamMetadata& metadata, bool consume) { const auto numValues = metadata.getNumValues(); const auto numBytes = static_cast(sizeof(T) * numValues); assert(numBytes == metadata.getByteLength()); out.resize(numValues); std::copy( tileData.getReadPosition(), tileData.getReadPosition() + numBytes, reinterpret_cast(out.data())); if (consume) { tileData.consume(numBytes); } } } // namespace mlt::util::decoding ================================================ FILE: cpp/src/mlt/util/rle.cpp ================================================ #include #include #include namespace mlt::util::decoding::rle { void decodeBoolean(BufferStream& tileData, std::vector& out, const metadata::stream::StreamMetadata& metadata, bool consume) { const auto bitCount = metadata.getNumValues(); const auto numBytes = (bitCount + 7) / 8; out.resize(numBytes); assert(metadata.getByteLength() <= tileData.getRemaining()); detail::ByteRleDecoder decoder{tileData.getReadPosition(), std::min(metadata.getByteLength(), tileData.getRemaining())}; decoder.next(out.data(), numBytes); assert(decoder.getBufferRemaining() == 0); if (consume) { tileData.consume(metadata.getByteLength()); } } } // namespace mlt::util::decoding::rle ================================================ FILE: cpp/src/mlt/util/rle.hpp ================================================ #pragma once #include #include #include #include #include namespace mlt::metadata::stream { class StreamMetadata; } namespace mlt::util::decoding::rle { namespace detail { // Borrowed from https://github.com/apache/orc, `/c++/src/ByteRLE.cc`, `ByteRleDecoderImpl::nextInternal` // Apache License 2.0 class ByteRleDecoder : public util::noncopyable { public: ByteRleDecoder() = delete; ByteRleDecoder(const std::uint8_t* buffer, size_t length) noexcept : bufferStart(reinterpret_cast(buffer)), bufferEnd(bufferStart + length) {} ByteRleDecoder(ByteRleDecoder&&) = delete; ByteRleDecoder& operator=(const ByteRleDecoder&) = delete; ByteRleDecoder& operator=(ByteRleDecoder&&) = delete; std::size_t getBufferRemaining() const noexcept { return bufferEnd - bufferStart; } void next(std::uint8_t* data, std::uint64_t numValues) { std::uint64_t position = 0; while (position < numValues) { // if we are out of values, read more if (remainingValues == 0) { readHeader(); } // how many do we read out of this block? size_t count = std::min(static_cast(numValues - position), remainingValues); uint64_t consumed = 0; if (repeating) { std::fill_n(data + position, count, value); consumed = count; } else { uint64_t i = 0; while (i < count) { uint64_t copyBytes = std::min(static_cast(count - i), static_cast(bufferEnd - bufferStart)); std::copy(bufferStart, bufferStart + copyBytes, data + position + i); if (!copyBytes) { // prevent infinite loop throw std::runtime_error("Unexpected end of buffer"); } bufferStart += copyBytes; i += copyBytes; } consumed = count; } remainingValues -= consumed; position += count; } } private: char readByte() { if (bufferStart == bufferEnd) { throw std::runtime_error("Unexpected end of buffer"); } return *bufferStart++; } void readHeader() { const std::uint8_t ch = readByte(); if (ch & (1 << 7)) { remainingValues = (ch ^ 0xff) + 1; repeating = false; } else { remainingValues = ch + MINIMUM_REPEAT; repeating = true; value = readByte(); } } static constexpr size_t MINIMUM_REPEAT = 3; size_t remainingValues = 0; char value = 0; const char* bufferStart; const char* bufferEnd; bool repeating = false; }; } // namespace detail /// Decode RLE bytes to a byte array /// @param tileData The source data /// @param out The target for output /// @param numBytes The number of bytes to write, and the size of `out` /// @param byteSize The number of bytes to consume from the source buffer /// @throws std::runtime_error The provided buffer does not contain enough data inline void decodeByte(BufferStream& tileData, std::uint8_t* out, std::uint32_t numBytes, std::uint32_t byteSize) { detail::ByteRleDecoder{tileData.getData(), tileData.getSize()}.next(out, numBytes); tileData.consume(byteSize); } /// Decode RLE bits to a vector /// @param consume Whether to consume input the source buffer (true) or leave it unchanged (false) void decodeBoolean(BufferStream&, std::vector&, const metadata::stream::StreamMetadata&, bool consume); /// Decode RLE bits to int/long values /// @param in The source data /// @param out The target for output, must already be sized to contain the resulting data /// @param numRuns The number of RLE runs in the input /// @throws std::runtime_error The provided buffer does not contain enough data template > requires(std::is_integral_v && (std::is_integral_v || std::is_enum_v) && sizeof(T) <= sizeof(TTarget) && std::regular_invocable && std::is_same_v, TTarget>) void decodeInt( const T* const in, const std::size_t inCount, TTarget* const out, const std::size_t outCount, const std::uint32_t numRuns, std::function convert = [](T x) { return static_cast(x); }) { std::uint32_t inOffset = 0; std::uint32_t outOffset = 0; for (std::uint32_t i = 0; i < numRuns; ++i) { if (inCount < inOffset + 2) { throw std::runtime_error("Unexpected end of buffer"); } const T runLength = in[inOffset]; const TTarget runValue = convert(in[inOffset++ + numRuns]); if (outCount < outOffset + runLength) { throw std::runtime_error("Unexpected end of buffer"); } std::fill_n(std::next(out, outOffset), runLength, runValue); outOffset += runLength; } } } // namespace mlt::util::decoding::rle ================================================ FILE: cpp/src/mlt/util/space_filling_curve.hpp ================================================ #pragma once #include #include #include namespace mlt::util { class SpaceFillingCurve { public: SpaceFillingCurve(std::int32_t minVertexValue, std::int32_t maxVertexValue) : coordinateShift((minVertexValue < 0) ? std::abs(minVertexValue) : 0), tileExtent(maxVertexValue + coordinateShift), numBits(static_cast(std::ceil(std::log2(tileExtent + 1)))), minBound(minVertexValue), maxBound(maxVertexValue) { // TODO: fix tile buffer problem } virtual ~SpaceFillingCurve() = default; virtual std::uint32_t encode(const Coordinate& vertex) const = 0; virtual Coordinate decode(std::uint32_t mortonCode) const = 0; std::uint32_t getNumBits() const noexcept { return numBits; } std::int32_t getCoordinateShift() const noexcept { return coordinateShift; } protected: void validate(const Coordinate& vertex) const { // TODO: also check for int overflow as we are limiting the sfc ids to max int size if (vertex.x < static_cast(minBound) || vertex.y < static_cast(minBound) || vertex.x > static_cast(maxBound) || vertex.y > static_cast(maxBound)) { throw std::runtime_error("The specified tile buffer size is currently not supported"); } } protected: const std::int32_t coordinateShift; const std::uint32_t tileExtent; const std::uint32_t numBits; const std::int32_t minBound; const std::int32_t maxBound; }; } // namespace mlt::util ================================================ FILE: cpp/src/mlt/util/vectorized.hpp ================================================ #pragma once #include #include #include namespace mlt::util::decoding::vectorized { namespace { /// JS/Java `>>>` template requires(std::integral) T unsigned_rshift(T t) { return static_cast(static_cast>(t) >> bits); } /// Shifts the ones bit into the sign, for reasons template requires(std::integral) T odd_sign(T t) { return (t << 31) >> 31; } template requires(std::integral) T shift_and_xor(T t) { return unsigned_rshift<1>(t) ^ odd_sign(t); } } // namespace template requires(std::is_integral_v && sizeof(T) == 4) inline void decodeComponentwiseDeltaVec2(T* const data, const std::size_t count) noexcept { assert((count % 2) == 0); if (1 < count) { data[0] = shift_and_xor(data[0]); data[1] = shift_and_xor(data[1]); for (std::size_t i = 2; i + 1 < count; i += 2) { data[i] = shift_and_xor(data[i]) + data[i - 2]; data[i + 1] = shift_and_xor(data[i + 1]) + data[i - 1]; } } } } // namespace mlt::util::decoding::vectorized ================================================ FILE: cpp/src/mlt/util/zigzag.hpp ================================================ #pragma once #include #include namespace mlt::util::decoding { template requires(std::is_integral_v>) T decodeZigZag(T encoded) noexcept { using UT = underlying_type_t; const auto signedValue = static_cast>(encoded); const auto unsignedValue = static_cast>(encoded); return static_cast((unsignedValue >> 1) ^ (-(signedValue & 1))); } } // namespace mlt::util::decoding ================================================ FILE: cpp/test/CMakeLists.txt ================================================ set(TEST_SOURCES test_decode.cpp test_fsst.cpp test_packed_bitset.cpp test_util.cpp test_varint.cpp ) if (MLT_WITH_FASTPFOR) list(APPEND TEST_SOURCES test_fastpfor.cpp) endif() add_executable(mlt-cpp-test ${TEST_SOURCES}) set_property(TARGET mlt-cpp-test PROPERTY CXX_STANDARD 20) # Tests have access to private headers target_include_directories(mlt-cpp-test PUBLIC "${PROJECT_SOURCE_DIR}/src") option(MLT_WITH_JSON "Include JSON support" ON) if(MLT_WITH_JSON) target_include_directories(mlt-cpp-test PUBLIC "${json_SOURCE_DIR}/include") target_compile_definitions(mlt-cpp-test PUBLIC MLT_WITH_JSON=1) endif(MLT_WITH_JSON) if(MLT_WITH_FASTPFOR) target_include_directories(mlt-cpp-test PRIVATE SYSTEM "${PROJECT_SOURCE_DIR}/vendor/fastpfor/headers") endif(MLT_WITH_FASTPFOR) set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) # For Windows: Prevent overriding the parent project's compiler/linker settings set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) add_subdirectory("${PROJECT_SOURCE_DIR}/vendor/googletest" "${CMAKE_CURRENT_BINARY_DIR}/googletest" EXCLUDE_FROM_ALL SYSTEM) target_link_libraries(mlt-cpp-test mlt-cpp gtest_main) include(GoogleTest) gtest_discover_tests( mlt-cpp-test WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} ) ================================================ FILE: cpp/test/test_decode.cpp ================================================ #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::string_literals; using namespace std::string_view_literals; #if MLT_WITH_JSON #include #include #endif namespace { std::vector findFiles(const std::filesystem::path& base, const std::regex& pattern) { std::vector results; for (const auto& entry : std::filesystem::directory_iterator(base)) { std::smatch match; const auto fileName = entry.path().filename().string(); if (entry.is_regular_file() && std::regex_match(fileName, match, pattern)) { results.push_back(entry.path()); } } return results; } std::vector loadFile(const std::filesystem::path& path) { std::ifstream file(path, std::ios::binary | std::ios::ate); if (file.is_open()) { const std::size_t size = file.tellg(); file.seekg(0); std::vector buffer(size); if (file.read(buffer.data(), size)) { return buffer; } } return {}; } bool writeFile(const std::filesystem::path& path, const std::string& data) { std::ofstream file(path, std::ios::binary | std::ios::out); if (file.is_open()) { file.write(data.data(), data.size()); return !file.fail(); } return false; } const auto basePath = std::filesystem::path("../test/expected/tag0x01"); #if MLT_WITH_JSON auto dump(const nlohmann::json& json) { return json.dump(2, ' ', false, nlohmann::json::error_handler_t::replace); } #endif std::pair, std::string> loadTile(const std::string& path) { const auto tileData = loadFile(path); if (tileData.empty()) { return {std::nullopt, "Failed to read tile data"}; } constexpr bool supportFastPFOR = true; auto tile = mlt::Decoder(supportFastPFOR).decode({(tileData.data()), tileData.size()}); #if MLT_WITH_JSON // Load the GeoJSON file, if present auto jsonBuffer = loadFile(path + ".geojson"); if (!jsonBuffer.empty()) { using json = nlohmann::json; const json expectedJSON = json::parse( jsonBuffer, nullptr, /*allow_exceptions=*/false, /*ignore_comments=*/true); // Convert the tile we loaded to GeoJSON const auto actualJSON = mlt::json::toJSON(tile, {.x = 3, .y = 5, .z = 7}, true); // Compare the two const auto diffJSON = mlt::util::diff(expectedJSON, actualJSON, {}); if (diffJSON.empty()) { std::error_code ec; std::filesystem::remove(path + ".diff.geojson", ec); } else { // Dump the unexpected results to files for review writeFile(path + ".new.geojson", dump(actualJSON)); writeFile(path + ".diff.geojson", dump(diffJSON)); std::cout << path << ":" << diffJSON.size() << " unexpected differences\n"; } } #endif // MLT_WITH_JSON return {std::move(tile), std::string()}; } } // namespace TEST(Decode, SimplePointBoolean) { const auto tile = loadTile(basePath / "simple/point-boolean.mlt").first; ASSERT_TRUE(tile); const auto* mltLayer = tile->getLayer("layer"); EXPECT_TRUE(mltLayer); EXPECT_EQ(mltLayer->getName(), "layer"); EXPECT_EQ(mltLayer->getExtent(), 4096); EXPECT_EQ(mltLayer->getFeatures().size(), 1); const auto& feature = mltLayer->getFeatures().front(); EXPECT_EQ(feature.getID(), 1); } TEST(Decode, SimpleLineBoolean) { const auto tile = loadTile(basePath / "simple/line-boolean.mlt"); // TODO: check properties, geometry, etc. ASSERT_TRUE(tile.first); } TEST(Decode, SimplePolygonBoolean) { const auto tile = loadTile(basePath / "simple/polygon-boolean.mlt"); ASSERT_TRUE(tile.first); const auto* layer = tile.first->getLayer("layer"); ASSERT_TRUE(layer); ASSERT_EQ(layer->getFeatures().size(), 1); const auto& geom = layer->getFeatures()[0].getGeometry(); EXPECT_EQ(geom.type, mlt::metadata::tileset::GeometryType::POLYGON); const auto& poly = dynamic_cast(geom); ASSERT_EQ(poly.getRings().size(), 1); // MLT stores rings without closing points; decoder must add them back const auto& ring = poly.getRings()[0]; EXPECT_EQ(ring.front(), ring.back()) << "polygon ring should be closed by decoder"; } TEST(Decode, SimpleMultiPointBoolean) { const auto tile = loadTile(basePath / "simple/multipoint-boolean.mlt"); ASSERT_TRUE(tile.first); } TEST(Decode, SimpleMultiLineBoolean) { const auto tile = loadTile(basePath / "simple/multiline-boolean.mlt"); ASSERT_TRUE(tile.first); } TEST(Decode, SimpleMultiPolygonBoolean) { const auto tile = loadTile(basePath / "simple/multipolygon-boolean.mlt"); ASSERT_TRUE(tile.first); } TEST(Decode, Bing) { const auto tile = loadTile(basePath / "bing/4-13-6.mlt"); ASSERT_TRUE(tile.first) << tile.second; } TEST(Decode, OMT) { const auto tile = loadTile(basePath / "omt/2_2_2.mlt"); ASSERT_TRUE(tile.first); } // Make sure everything else loads without errors TEST(Decode, AllBing) { const std::regex metadataFilePattern{".*\\.mlt"}; for (const auto& path : findFiles(basePath / "bing", metadataFilePattern)) { std::cout << " Loading " << path.filename().string() << " ...\n"; loadTile(path); } } TEST(Decode, AllAmazon) { const std::regex metadataFilePattern{".*\\.mlt"}; for (const auto& path : findFiles(basePath / "amazon", metadataFilePattern)) { std::cout << " Loading " << path.filename().string() << " ...\n"; loadTile(path); } } TEST(Decode, AllOMT) { const std::regex metadataFilePattern{".*\\.mlt"}; for (const auto& path : findFiles(basePath / "omt", metadataFilePattern)) { try { if (auto result = loadTile(path); result.first) { std::cout << "Loaded: " << path.filename().string() << "\n"; } else { std::cout << "Not Loaded: " << path.filename().string() << ": " << result.second << "\n"; } } catch (const std::exception& e) { FAIL() << path.filename().string() << " Failed: " << e.what(); } } } ================================================ FILE: cpp/test/test_fastpfor.cpp ================================================ #if !MLT_WITH_FASTPFOR #error This file should be excluded when FastPFor support is not enabled #endif #include // From fastpfor/... #include #include #include #if MLT_WITH_FASTPFOR_SIMD #include #endif // MLT_WITH_FASTPFOR_SIMD #include #include #include #include #include // Test less than one block of data, which does not use both parts of the CompositeCodec TEST(FastPfor, DecompressPartial) { const std::vector data = {0, 9605520}; const std::vector expected = {16, 17, 18}; FastPForLib::CompositeCodec, FastPForLib::VariableByte> codec; const auto result = codec.uncompress(data, expected.size()); EXPECT_TRUE(std::ranges::equal(result, expected)); } namespace { // Return a sequence of 1,2,1,2,... of length n auto simpleValueView(std::size_t n) { return std::views::iota(0ul) | std::views::transform([](auto x) { return (x & 1u) + 1u; }) | std::views::take(n) | std::views::common; } auto simpleValues(std::size_t n) { const auto view = simpleValueView(n); return std::vector(view.begin(), view.end()); } } // namespace // Check decode of data encoded by the Java implementation with a payload larger than, // but not a multiple of, the block size. The result was captured before the final // byte-swapping, so no additional swapping is needed as with a typical payload. TEST(FastPfor, JavaV1) { const std::uint32_t javaEncodedByteSwapped[] = { 0x00000300ul, 0x00000031ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x99999999ul, 0x00000006ul, 0x00020002ul, 0x00000002ul, 0x00000000ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, 0x82818281ul, }; std::vector decoded(1000); std::size_t outSize = decoded.size(); FastPForLib::CompositeCodec, FastPForLib::VariableByte> codec; codec.decodeArray(reinterpret_cast(javaEncodedByteSwapped), sizeof(javaEncodedByteSwapped) / sizeof(uint32_t), decoded.data(), outSize); EXPECT_EQ(outSize, decoded.size()); EXPECT_EQ(decoded, simpleValues(decoded.size())); } #if MLT_WITH_FASTPFOR_SIMD namespace { template void testSIMDInterop(int valuesCount) { FastPForLib::CompositeCodec, FastPForLib::VariableByte> codec; FastPForLib::CompositeCodec, FastPForLib::VariableByte> simd_codec; const auto values = simpleValues(valuesCount); const auto encoded = codec.compress(values); const auto simd_encoded = simd_codec.compress(values); { SCOPED_TRACE("Plain uncompress of plain compress"); EXPECT_EQ(codec.uncompress(encoded, values.size()), values); } { SCOPED_TRACE("SIMD uncompress of SIMD compress"); EXPECT_EQ(simd_codec.uncompress(simd_encoded, values.size()), values); } { SCOPED_TRACE("SIMD uncompress of plain compress"); const auto decoded = simd_codec.uncompress(encoded, values.size()); if (valuesCount < 32 * BLOCK_SIZE) { EXPECT_EQ(decoded, values); } else { EXPECT_NE(decoded, values); } } { SCOPED_TRACE("Plain uncompress of SIMD compress"); const auto decoded = codec.uncompress(simd_encoded, values.size()); if (valuesCount < 32 * BLOCK_SIZE) { EXPECT_EQ(decoded, values); } else { EXPECT_NE(decoded, values); } } } struct SIMDInteropCase { int values; int blockSize; }; std::vector simdInteropCases() { constexpr int valueCounts[] = {96, 128, 160, 256, 384}; constexpr int blockSizes[] = {4, 8}; std::vector cases; cases.reserve(std::size(valueCounts) * std::size(blockSizes)); for (const int values : valueCounts) { for (const int blockSize : blockSizes) { cases.push_back({values, blockSize}); } } return cases; } std::string simdInteropCaseName(const ::testing::TestParamInfo& info) { return "Values_" + std::to_string(info.param.values) + "_BlockSize_" + std::to_string(info.param.blockSize); } class PFORSIMDInteropTest : public ::testing::TestWithParam {}; TEST_P(PFORSIMDInteropTest, Interop) { const auto testCase = GetParam(); switch (testCase.blockSize) { case 4: testSIMDInterop<4>(testCase.values); break; case 8: testSIMDInterop<8>(testCase.values); break; default: FAIL() << "Unsupported block size: " << testCase.blockSize; } } INSTANTIATE_TEST_SUITE_P(FastPfor, PFORSIMDInteropTest, ::testing::ValuesIn(simdInteropCases()), simdInteropCaseName); } // namespace #endif // MLT_WITH_FASTPFOR_SIMD ================================================ FILE: cpp/test/test_fsst.cpp ================================================ #include #include #include #include TEST(FSST, DecodeFromJava) { const std::string expected = "AAAAAAABBBAAACCdddddEEEEEEfffEEEEAAAAAddddCC"; const std::vector symbols = {65, 65, 69, 69, 100, 100, 65, 66, 67, 69, 100, 102}; const std::vector symbolLengths = {2, 2, 2, 1, 1, 1, 1, 1, 1}; const std::vector javaCompressed = {0, 0, 0, 3, 4, 4, 4, 0, 3, 5, 5, 2, 2, 7, 1, 1, 1, 8, 8, 8, 1, 1, 0, 0, 3, 2, 2, 5, 5}; const auto decoded = mlt::decoder::StringDecoder::decodeFSST( symbols, symbolLengths, javaCompressed, expected.size()); EXPECT_EQ(decoded.size(), expected.size()); EXPECT_EQ(0, memcmp(expected.c_str(), decoded.data(), expected.size())); // also make sure buffer growth works const auto decoded2 = mlt::decoder::StringDecoder::decodeFSST(symbols, symbolLengths, javaCompressed, 0); EXPECT_EQ(decoded2.size(), expected.size()); EXPECT_EQ(0, memcmp(expected.c_str(), decoded2.data(), expected.size())); const auto decoded3 = mlt::decoder::StringDecoder::decodeFSST( symbols, symbolLengths, javaCompressed, expected.size() / 2); EXPECT_EQ(decoded3.size(), expected.size()); EXPECT_EQ(0, memcmp(expected.c_str(), decoded3.data(), expected.size() / 2)); } ================================================ FILE: cpp/test/test_packed_bitset.cpp ================================================ #include #include using namespace mlt; TEST(PackedBitset, TestBit) { EXPECT_FALSE(testBit({}, 0)); EXPECT_FALSE(testBit({}, 1)); EXPECT_FALSE(testBit({}, 1000)); EXPECT_FALSE(testBit({0xf0}, 0)); EXPECT_TRUE(testBit({0xf0}, 7)); EXPECT_FALSE(testBit({0xf0}, 8)); EXPECT_TRUE(testBit({0xf0, 0x01}, 8)); } TEST(PackedBitset, NextBit) { for (const auto i : {0, 7, 8}) { EXPECT_FALSE(nextSetBit({}, i)); EXPECT_FALSE(nextSetBit({0}, i)); EXPECT_FALSE(nextSetBit({0, 0}, i)); } EXPECT_EQ(nextSetBit({0xaa}, 0), 1); EXPECT_EQ(nextSetBit({0xaa}, 1), 1); EXPECT_EQ(nextSetBit({0xaa}, 2), 3); EXPECT_EQ(nextSetBit({0xaa}, 3), 3); EXPECT_EQ(nextSetBit({0xaa}, 4), 5); EXPECT_EQ(nextSetBit({0xaa}, 5), 5); EXPECT_EQ(nextSetBit({0xaa}, 6), 7); EXPECT_EQ(nextSetBit({0xaa}, 7), 7); EXPECT_FALSE(nextSetBit({0xaa}, 8)); EXPECT_EQ(nextSetBit({0xaa, 0xaa}, 8), 9); EXPECT_EQ(nextSetBit({0x01, 0xc0}, 1), 14); } TEST(PackedBitset, CountBits) { EXPECT_EQ(countSetBits({}), 0); EXPECT_EQ(countSetBits({0}), 0); EXPECT_EQ(countSetBits({0, 0}), 0); EXPECT_EQ(countSetBits({0x01}), 1); EXPECT_EQ(countSetBits({0x02}), 1); EXPECT_EQ(countSetBits({0x04}), 1); EXPECT_EQ(countSetBits({0x08}), 1); EXPECT_EQ(countSetBits({0x10}), 1); EXPECT_EQ(countSetBits({0x20}), 1); EXPECT_EQ(countSetBits({0x40}), 1); EXPECT_EQ(countSetBits({0x80}), 1); EXPECT_EQ(countSetBits({0x01, 0}), 1); EXPECT_EQ(countSetBits({0, 0, 0, 4}), 1); EXPECT_EQ(countSetBits({0xff}), 8); } ================================================ FILE: cpp/test/test_util.cpp ================================================ #include #include TEST(Util, ComponentwiseDeltaVec2) { // input, expected output const std::vector, std::vector>> componentwiseDeltaVec2Cases{ {{10, 14, 3, 9}, {5, 7, 3, 2}}, {{6, 12, 10, 12, 24, 44}, {3, 6, 8, 12, 20, 34}}, {{0, 0, 8192, 0, 0, 8192, 8191, 0}, {0, 0, 4096, 0, 4096, 4096, 0, 4096}}, {{ 1416, 520, 1888, 6448, 2927, 1136, 224, 47, 5920, 4671, 752, 351, 1999, 1423, 447, 671, 1184, 1792, 143, 351, 623, 320, 95, 1055, 976, 880, 1407, 1471, 3983, 336, 703, 80, 1680, 559, 15, 1120, 1279, 848, 1312, 1280, 1055, 528, 511, 976, 1072, 175, 1072, 1423, 976, 352, 463, 416, 2527, 2896, 2192, 1167, }, { 708, 260, 1652, 3484, 188, 4052, 300, 4028, 3260, 1692, 3636, 1516, 2636, 804, 2412, 468, 3004, 1364, 2932, 1188, 2620, 1348, 2572, 820, 3060, 1260, 2356, 524, 364, 692, 12, 732, 852, 452, 844, 1012, 204, 1436, 860, 2076, 332, 2340, 76, 2828, 612, 2740, 1148, 2028, 1636, 2204, 1404, 2412, 140, 3860, 1236, 3276, }}, {{ 558, 7970, 72, 13, 3766, 6579, 100, 34, 8, 90, 134, 78, 92, 33, 76, 0, 28, 25, 84, 22, 52, 13, 12, 41, 80, 9, 50, 34, 44, 1, 2, 79, 50, 21, 28, 47, 30, 17, 4, 25, 58, 53, 90, 7, 48, 14, 96, 19, 18, 20, 118, 11, 46, 49, 12, 71, 16, 11, 711, 1277, 15, 32, 15, 8, 13, 34, 13, 4, 25, 26, 17, 2, 1, 8, 8, 12, 5, 8, 5, 1, 0, 16, 9, 2, 0, 14, 7, 4, 0, 6, 5, 6, 0, 4, 9, 1, 9, 10, 14, 0, 0, 4, 7, 6, 9, 3, 3, 8, 17, 11, 1, 4, 11, 3, 1, 10, 19, 12, 3, 3, 2, 3, 15, 3, 0, 8, 19, 2, 25, 32, 18, 8, 0, 8, 37, 3, 9, 8, 5, 18, 19, 0, 5, 6, 3, 18, 13, 0, 0, 20, 7, 0, 7, 9, 13, 6, 11, 7, 9, 2, 0, 12, 7, 6, 17, 2, 45, 28, 19, 26, 10, 14, 7, 4, 11, 5, 19, 8, 13, 11, 11, 20, 23, 20, 13, 4, 13, 32, 37, 16, 17, 24, 23, 18, 5, 12, 33, 26, 13, 20, 4, 36, 49, 38, 17, 46, 1390, 1536, 15, 1679, 2447, 7360, }, { 279, 3985, 315, 3978, 2198, 688, 2248, 705, 2252, 750, 2319, 789, 2365, 772, 2403, 772, 2417, 759, 2459, 770, 2485, 763, 2491, 742, 2531, 737, 2556, 754, 2578, 753, 2579, 713, 2604, 702, 2618, 678, 2633, 669, 2635, 656, 2664, 629, 2709, 625, 2733, 632, 2781, 622, 2790, 632, 2849, 626, 2872, 601, 2878, 565, 2886, 559, 2530, -80, 2522, -64, 2514, -60, 2507, -43, 2500, -41, 2487, -28, 2478, -27, 2477, -23, 2481, -17, 2478, -13, 2475, -14, 2475, -6, 2470, -5, 2470, 2, 2466, 4, 2466, 7, 2463, 10, 2463, 12, 2458, 11, 2453, 16, 2460, 16, 2460, 18, 2456, 21, 2451, 19, 2449, 23, 2440, 17, 2439, 19, 2433, 17, 2432, 22, 2422, 28, 2420, 26, 2421, 24, 2413, 22, 2413, 26, 2403, 27, 2390, 43, 2399, 47, 2399, 51, 2380, 49, 2375, 53, 2372, 62, 2362, 62, 2359, 65, 2357, 74, 2350, 74, 2350, 84, 2346, 84, 2342, 79, 2335, 82, 2329, 78, 2324, 79, 2324, 85, 2320, 88, 2311, 89, 2288, 103, 2278, 116, 2283, 123, 2279, 125, 2273, 122, 2263, 126, 2256, 120, 2250, 130, 2238, 140, 2231, 142, 2224, 158, 2205, 166, 2196, 178, 2184, 187, 2181, 193, 2164, 206, 2157, 216, 2159, 234, 2134, 253, 2125, 276, 2820, 1044, 2812, 204, 1588, 3884, }}}; using namespace mlt::util::decoding::vectorized; for (std::size_t i = 0; i < componentwiseDeltaVec2Cases.size(); ++i) { const auto& [input, expected] = componentwiseDeltaVec2Cases[i]; auto output = input; decodeComponentwiseDeltaVec2(output.data(), output.size()); EXPECT_EQ(output, expected); } } ================================================ FILE: cpp/test/test_varint.cpp ================================================ #include #include #include #include using namespace mlt; namespace { template T decode(std::vector data) { mlt::BufferStream buffer{std::string_view{reinterpret_cast(data.data()), data.size()}}; return mlt::util::decoding::decodeVarint(buffer); } } // namespace TEST(Varint, Size) { using namespace mlt::util::decoding; EXPECT_EQ(getVarintSize(0u), 1); EXPECT_EQ(getVarintSize(1u), 1); EXPECT_EQ(getVarintSize(0x7fu), 1); EXPECT_EQ(getVarintSize(0x80u), 2); EXPECT_EQ(getVarintSize(0x3fffu), 2); EXPECT_EQ(getVarintSize(0x4000u), 3); EXPECT_EQ(getVarintSize(0xffffffffu), 5); EXPECT_EQ(getVarintSize(0x800000000ull), 6); } TEST(Varint, Decode) { EXPECT_EQ(decode({0x0}), 0x0); EXPECT_EQ(decode({0x7f}), 0x7f); EXPECT_EQ(decode({0x80, 1}), 0x80); EXPECT_EQ(decode({0xff, 0x7f}), 0x3fff); EXPECT_THROW(decode({0xff, 0x80}), std::runtime_error); EXPECT_EQ(decode({0x80, 0x80, 0x01}), 0x4000); EXPECT_EQ(decode({0xff, 0xff, 0x03}), 0xffff); EXPECT_EQ(decode({0xff, 0xff, 0xff, 0xff, 0x0f}), 0xffffffff); EXPECT_EQ(decode({0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01}), 0xffffffffffffffffULL); EXPECT_THROW(decode({0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02}), std::runtime_error); } ================================================ FILE: cpp/tool/CMakeLists.txt ================================================ add_executable(mlt-cpp-json mlt-json.cpp synthetic-geojson.cpp) set_property(TARGET mlt-cpp-json PROPERTY CXX_STANDARD 20) set(MLT_WITH_JSON ON CACHE BOOL "Include JSON Support" FORCE) target_link_libraries(mlt-cpp-json PRIVATE mlt-cpp) ================================================ FILE: cpp/tool/mlt-json.cpp ================================================ #include #include #include #include #include #include using namespace std::string_literals; using namespace std::string_view_literals; #include #include "synthetic-geojson.hpp" namespace { std::vector loadFile(const std::string& path) { std::ifstream file(path, std::ios::binary | std::ios::ate); if (file.is_open()) { const std::size_t size = file.tellg(); file.seekg(0); std::vector buffer(size); if (file.read(buffer.data(), size)) { return buffer; } } return {}; } } // namespace int main(int argc, char** argv) { if ((argc != 2 && argc != 5) || !std::strcmp(argv[1], "--help")) { std::cerr << "Decode a MapLibre Vector Tile and output it as JSON or GeoJSON.\n" << "GeoJSON is used if tile coordinates are provided.\n\n" << "Usage: " << argv[0] << " [ ]\n"; return 1; } std::uint32_t x = 0; std::uint32_t y = 0; std::uint32_t z = 0; const bool geoJSON = (argc == 5); if (geoJSON) { z = static_cast(std::stoul(argv[2])); x = static_cast(std::stoul(argv[3])); y = static_cast(std::stoul(argv[4])); } const std::string baseName = argv[1]; auto buffer = loadFile(baseName); if (buffer.empty()) { std::cerr << "Failed to load " + baseName + "\n"; return 1; } mlt::Decoder decoder(/*supportFastPFOR=*/true); const auto tileData = decoder.decode({buffer.data(), buffer.size()}); nlohmann::json tileJSON; if (geoJSON) { tileJSON = mlt::json::toJSON(tileData, {.x = x, .y = y, .z = z}, true); } else { tileJSON = mlt::test::toFeatureCollection(tileData); } std::cout << tileJSON.dump(2, ' ', false, nlohmann::json::error_handler_t::replace); return 0; } ================================================ FILE: cpp/tool/synthetic-geojson.cpp ================================================ #include "synthetic-geojson.hpp" #include #include #include #include #include #include namespace mlt::test { using json = nlohmann::json; namespace { /// Serialize a coordinate value as an integer if it is a whole number, otherwise as a float json buildRawCoordValue(float v) { auto i = static_cast(v); return (v == static_cast(i)) ? json(i) : json(v); } json buildRawCoordinateArray(const Coordinate& coord) { return json::array({buildRawCoordValue(coord.x), buildRawCoordValue(coord.y)}); } json buildRawCoordinatesArray(const CoordVec& coords) { return mlt::json::detail::buildArray(coords, [](const auto& c) { return buildRawCoordinateArray(c); }); } json buildRawPolygonCoords(const std::vector& rings) { return mlt::json::detail::buildArray(rings, [](const auto& ring) { return buildRawCoordinatesArray(ring); }); } json buildRawGeometryElement(const geometry::Geometry& geometry) { using metadata::tileset::GeometryType; switch (geometry.type) { case GeometryType::POINT: { const auto& g = static_cast(geometry); return {{"type", "Point"}, {"coordinates", buildRawCoordinateArray(g.getCoordinate())}}; } case GeometryType::LINESTRING: { const auto& g = static_cast(geometry); return {{"type", "LineString"}, {"coordinates", buildRawCoordinatesArray(g.getCoordinates())}}; } case GeometryType::POLYGON: { const auto& g = static_cast(geometry); return {{"type", "Polygon"}, {"coordinates", buildRawPolygonCoords(g.getRings())}}; } case GeometryType::MULTIPOINT: { const auto& g = static_cast(geometry); return {{"type", "MultiPoint"}, {"coordinates", buildRawCoordinatesArray(g.getCoordinates())}}; } case GeometryType::MULTILINESTRING: { const auto& g = static_cast(geometry); return {{"type", "MultiLineString"}, {"coordinates", mlt::json::detail::buildArray(g.getLineStrings(), [](const auto& ls) { return buildRawCoordinatesArray(ls); })}}; } case GeometryType::MULTIPOLYGON: { const auto& g = static_cast(geometry); return {{"type", "MultiPolygon"}, {"coordinates", mlt::json::detail::buildArray(g.getPolygons(), [](const auto& poly) { return buildRawPolygonCoords(poly); })}}; } default: throw std::runtime_error("Unsupported geometry type " + std::to_string(std::to_underlying(geometry.type))); } } } // anonymous namespace json toFeatureCollection(const MapLibreTile& tile) { auto features = json::array(); for (const auto& layer : tile.getLayers()) { for (const auto& feature : layer.getFeatures()) { auto props = mlt::json::detail::buildProperties(layer, feature); props["_extent"] = layer.getExtent(); props["_layer"] = layer.getName(); auto featureObj = json{ {"geometry", buildRawGeometryElement(feature.getGeometry())}, {"properties", std::move(props)}, {"type", "Feature"}, }; if (const auto id = feature.getID(); id.has_value()) { featureObj["id"] = *id; } features.push_back(std::move(featureObj)); } } return {{"features", std::move(features)}, {"type", "FeatureCollection"}}; } } // namespace mlt::test ================================================ FILE: cpp/tool/synthetic-geojson.hpp ================================================ #pragma once #include #include /// Test utility: convert an MLT tile to a flat GeoJSON FeatureCollection with raw tile-space /// coordinates. Layer name and extent are stored as `_layer` and `_extent` in each feature's /// properties. This matches the format used by test/synthetic expected JSON files. namespace mlt::test { nlohmann::json toFeatureCollection(const MapLibreTile& tile); } // namespace mlt::test ================================================ FILE: cpp/tool/synthetic.test.ts ================================================ import { execFileSync } from "node:child_process"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { compareWithTolerance, getTestCases, writeActualOutput, } from "synthetic-test-utils"; import { describe, expect, it } from "vitest"; const __dirname = dirname(fileURLToPath(import.meta.url)); const binary = resolve(__dirname, "../build/tool/mlt-cpp-json"); const SKIPPED_TESTS = []; describe("MLT Decoder - Synthetic tests", () => { expect.addEqualityTesters([compareWithTolerance]); const testCases = getTestCases(SKIPPED_TESTS); for (const { name, content, fileName } of testCases.active) { it(name, async () => { const actual = await decodeMLT(fileName); try { expect(actual).toEqual(content); } catch (error) { writeActualOutput(fileName, actual); throw error; } }); } for (const skippedTest of testCases.skipped) { it.skip(skippedTest, () => { // Test is skipped since it is not supported yet }); } }); async function decodeMLT(mltFilePath: string) { const output = execFileSync(binary, [mltFilePath], { encoding: "utf-8" }); return JSON.parse(output); } ================================================ FILE: cpp/vendor/fastpfor.cmake ================================================ if(NOT MLT_WITH_FASTPFOR) message(STATUS "[MLT] No FastPFOR support") return() endif(NOT MLT_WITH_FASTPFOR) message(STATUS "[MLT] Including FastPFOR support") # The fastpfor gtest targets conflict with ours set(FASTPFOR_WITH_TEST OFF CACHE BOOL "Disable tests in FastPFor" FORCE) add_subdirectory("${PROJECT_SOURCE_DIR}/vendor/fastpfor" "${CMAKE_CURRENT_BINARY_DIR}/fastpfor" EXCLUDE_FROM_ALL SYSTEM) # Disable all warnings for FastPFOR if(MSVC) target_compile_options(FastPFOR PRIVATE /w) else() target_compile_options(FastPFOR PRIVATE -w) endif() target_link_libraries(mlt-cpp FastPFOR) target_include_directories(mlt-cpp PRIVATE SYSTEM "${PROJECT_SOURCE_DIR}/vendor/fastpfor/headers") target_compile_definitions(mlt-cpp PUBLIC MLT_WITH_FASTPFOR=1) if(MLT_WITH_FASTPFOR_SIMD) target_compile_definitions(mlt-cpp PUBLIC MLT_WITH_FASTPFOR_SIMD=1) endif(MLT_WITH_FASTPFOR_SIMD) list(APPEND MLT_EXPORT_TARGETS FastPFOR) ================================================ FILE: docs/assets/extra.css ================================================ [data-md-color-scheme="default"] { --md-primary-fg-color: #295daa; --md-accent-fg-color: #568ad6; } [data-md-color-scheme="slate"] { --md-primary-fg-color: #295daa; --md-accent-fg-color: #568ad6; } .md-nav__title { display: none; } .md-nav--primary .md-nav__link[for="__toc"] > .md-nav__icon, .md-nav--primary .md-nav__link[for="__toc"] ~ .md-nav { display: none; } .md-copyright__highlight { color: inherit; } .inline-attribution { font-size: smaller; } .experimental { background-color: #ffeb3b; color: #333; padding: 2px 6px; border-radius: 3px; font-size: 0.8em; font-weight: bold; margin-left: 4px; } .experimental::before { content: "experimental"; } [data-md-color-scheme="slate"] .experimental { background-color: #ffc107; color: #000; } ================================================ FILE: docs/assets/spec/mlt_tileset_metadata.json ================================================ { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "version": { "type": "integer" }, "name": { "type": "string" }, "description": { "type": "string" }, "attribution": { "type": "string" }, "minZoom": { "type": "integer" }, "maxZoom": { "type": "integer" }, "bounds": { "type": "array", "items": { "type": "object", "properties": { "left": { "type": "number" }, "top": { "type": "number" }, "right": { "type": "number" }, "bottom": { "type": "number" } }, "required": [ "left", "top", "right", "bottom" ] } }, "center": { "type": "array", "items": { "type": "object", "properties": { "latitude": { "type": "number" }, "longitude": { "type": "number" } }, "required": [ "latitude", "longitude" ] } } }, "required": [ "version" ] } ================================================ FILE: docs/assets/spec/place_feature.json ================================================ { "title": "Place Feature", "type": "object", "properties": { "id": { "type": "integer" }, "geometry": { "type": "MultiPolygon" }, "properties": { "type": "object", "properties": { "speed": { "type": "number" }, "rank": { "type": "integer" }, "name": { "type": "object", "properties": { "en": { "type": "string" }, "de": { "type": "string" } }, "required": ["en"] } } } }, "required": ["geometry"] } ================================================ FILE: docs/encodings.md ================================================

Encoding Definitions

[TOC] --- # Plain No compression is applied to the data. Depending on the data type, values are stored in the following formats: - **Integer**: Little-Endian byte order - **Long**: Little-Endian byte order - **Float**: IEEE754 floating-point numbers in Little-Endian byte order - **Double**: IEEE754 floating-point numbers in Little-Endian byte order - **String**: Length and data streams # Boolean-RLE This encoding compresses boolean columns using least-significant bit numbering (bit-endianness). Refer to the [ORC specification](https://orc.apache.org/specification/ORCv1/#boolean-run-length-encoding) for implementation details. # Byte-RLE This encoding compresses byte streams, such as the `GeometryType` stream in Geometry columns. Refer to the [ORC specification](https://orc.apache.org/specification/ORCv1/#byte-run-length-encoding) for implementation details. # Dictionary Encoding Dictionary encoding compactly represents repeated values and can be applied to `String` and `Geometry` columns. Distinct values are stored in a `dictionary` stream, while a separate `data` stream stores indices into the dictionary. We allow the following encodings of this concept: ## String Dictionary Encoding The dictionary stream contains distinct UTF-8 encoded string values. The data stream contains dictionary indices encoded as `UInt32`. A dictionary-encoded nullable string column consists of the following streams in order: `Present`, `Length`, `Dictionary`, `Data` !!! TODO Is "can" (⇒ MAY) here the right terminology? This implies that streams might be encoded by one of the options below, but also other options. If yes, what is the alternative options? If no, clarify this misunderstanding. All streams can be further compressed using lightweight encodings: - **Present Stream**: Boolean-RLE - **Length and Data Stream**: See Integer encodings - **Dictionary**: See FSST Dictionary ### FSST Dictionary Encoding Dictionary encoding requires fully repeating strings to reduce size. However, geospatial attributes often contain strings with common prefixes that are not identical (e.g., localized country names). FSST replaces frequently occurring substrings while supporting efficient scans and random lookups. It further compresses UTF-8 encoded dictionary values in MLT. An FSST dictionary-encoded nullable string column consists of the following streams in order: `Present`, `SymbolLength`, `SymbolTable`, `String Length`, `Dictionary` (Compressed Corpus) For implementation details, refer to [this paper](https://www.vldb.org/pvldb/vol13/p2649-boncz.pdf). **Available implementations**: - **C++**: - **Java**: Work in progress - **JS/WebAssembly decoder**: Work in progress (simple to implement) ### Shared Dictionary Encoding Shared dictionary encoding allows multiple columns to share a common dictionary. Nested fields can also use a shared dictionary (e.g., for localized values like `name:*` columns in an OSM dataset). If used for nested fields, all fields sharing the dictionary must be grouped together in the file and prefixed with the dictionary. A shared dictionary-encoded nullable string column consists of the following streams in order: `Length`, `Dictionary`, `Present1`, `Data1`, `Present2`, `Data2` A shared FSST dictionary-encoded nullable string column consists of the following streams in order: `SymbolLength`, `SymbolTable`, `String Length`, `Dictionary` (Compressed Corpus), `Present1`, `Data1`, `Present2`, `Data2` ## Vertex Dictionary Encoding Uses an additional `VertexOffsets` stream to store indices for vertex coordinates in the `VertexBuffer` stream. Vertices in the VertexBuffer are sorted using a Hilbert curve and delta-encoded with null suppression. ### Morton Vertex Dictionary Encoding `VertexBuffer` coordinates are transformed into a 1D integer using Morton code. The data is sorted by Morton code and further compressed using integer compression techniques. # Integer Encodings Most data in MLT is stored as integer arrays, making efficient integer compression crucial. ## Logical-Level Techniques Integers are encoded using delta encoding, run-length encoding (RLE), or a combination of both (delta-RLE). ### Delta Encoding Computes differences between consecutive elements (e.g., x₂ - x₁, x₃ - x₂, ...). Used with [physical-level techniques](#physical-level-techniques) to reduce the number of bits required for storing delta values. ### Run-Length Encoding (RLE) Refer to [Wikipedia](https://en.wikipedia.org/wiki/Run-length_encoding) for a basic explanation. Runs and values are stored in separate buffers. For unsigned integers, ZigZag encoding is applied to the values buffer. Both buffers are further compressed using null suppression. ### Delta-RLE Applies delta encoding followed by RLE. Efficient for ascending sequences like `id` fields. ## Physical-Level Techniques Null suppression techniques are used to compress integer arrays by reducing the number of bits required to store each integer. ### ZigZag Encoding Used for encoding unsigned integers in null suppression techniques. [ZigZag encoding](https://en.wikipedia.org/wiki/Variable-length_quantity#Zigzag_encoding) uses the least significant bit to represent the sign. ### VarInt Encoding A byte-aligned null suppression technique that compresses integers using a minimal number of bytes. For implementation details, refer to [Protobuf](https://protobuf.dev/programming-guides/encoding/#varints). ### SIMD-FastPFOR A bit-aligned null suppression technique that compresses integers using a minimal number of bits. Uses a patched approach to store exceptions (outliers) separately, keeping the overall bit width small. Refer to: - - **Available implementations**: - **C++**: - **Java**: - **C#**: - **JS/WebAssembly**: Work in progress (higher implementation complexity) ================================================ FILE: docs/implementation-status.md ================================================ # Implementation Status ## Integrations | Integration | Status | |---|---| | [MapLibre Native](https://maplibre.org/maplibre-native/) | Supported since MapLibre Android 12.1.0, MapLibre iOS 6.2.0. PR: [#3246](https://github.com/maplibre/maplibre-native/pull/3246). | | [MapLibre GL JS](https://maplibre.org/maplibre-gl-js/) | Supported since version 5.12.0. PR: [#6570](https://github.com/maplibre/maplibre-gl-js/pull/6570). | | Planetiler | Can generate tiles in MLT since version [0.10.0](https://github.com/onthegomap/planetiler/releases/tag/v0.10.0). | | PMTiles | Can store MLT, see [PMTiles v3 spec - Tile Type](https://github.com/protomaps/PMTiles/blob/main/spec/v3/spec.md#tile-type-tt). | | [Martin tile server](https://maplibre.org/martin/) | Can detect and serve MLT since version [1.3.0](https://github.com/maplibre/martin/releases/tag/martin-v1.3.0). PR: [#2512](https://github.com/maplibre/martin/pull/2512). | ## Implementations | Implementation | Language | Type | Notes | |---|---|---|---| | [maplibre-tile-spec/cpp](https://github.com/maplibre/maplibre-tile-spec/tree/main/cpp) | C++ | Decoder | Used by MapLibre Native | | [maplibre-tile-spec/js](https://github.com/maplibre/maplibre-tile-spec/tree/main/js) | TypeScript | Decoder | Used by MapLibre GL JS | | [maplibre-tile-spec/java](https://github.com/maplibre/maplibre-tile-spec/tree/main/java) | Java | Encoder | | | [maplibre-tile-spec/rust/mlt](https://github.com/maplibre/maplibre-tile-spec/tree/main/rust/mlt) | Rust | Decoder / Encoder | | ================================================ FILE: docs/index.md ================================================ # Introduction --8<-- "live-spec-note" MLT is natively supported by [MapLibre GL JS](https://maplibre.org/maplibre-gl-js/) and [MapLibre Native](https://maplibre.org/maplibre-native/), and tiles can be served using the [Martin tile server](https://maplibre.org/martin/). The MLT format is mainly inspired by the [MVT format](https://github.com/mapbox/vector-tile-spec), but has been redesigned from the ground up to improve the following areas: - **Improved compression ratio** - up to 6x on large tiles, based on a column oriented layout with (custom) lightweight encodings - **Better decoding performance** - fast lightweight encodings which can be used in combination with SIMD/vectorization instructions - **Support for linear referencing and m-values** to efficiently support the upcoming next generation source formats such as Overture Maps (GeoParquet) - **Support 3D coordinates**, i.e. elevation - **Support complex types**, including nested properties, lists and maps - **Improved processing performance**: Based on an in-memory format that can be processed efficiently on the CPU and GPU and loaded directly into GPU buffers partially (like polygons in WebGL) or completely (in case of WebGPU compute shader usage) without additional processing ================================================ FILE: docs/snippets/live-spec-note ================================================ !!! NOTE This is a live specification that evolves continuously. Features marked as are under active development and may change in future versions. Stable features are those without experimental tags. ================================================ FILE: docs/specification.md ================================================

MapLibre Tile Specification

--8<-- "live-spec-note" [TOC] --- # Basics An MLT (MapLibre Tile) contains information about a specific geographic region, known as a tile. Each tile is a collection of `FeatureTables`, which are equivalent to `Layers` in the [MVT specification](https://github.com/mapbox/vector-tile-spec). A `FeatureTable` contains thematically grouped vector data, known as `Features`. A `FeatureTable` can contain up to `2^31 - 1` (2,147,483,647) features; this limit is chosen so the feature count fits in signed 32-bit integer fields used by decoders. Features within a single FeatureTable share a common set of attribute columns (properties) and typically share the same geometry type (though this is not strictly required). Each `FeatureTable` is preceded by a `FeatureTableMetadata` that describes `FeatureTable`'s structure. The visual appearance of a tile is usually defined by a [MapLibre Style](https://maplibre.org/maplibre-style-spec/), which specifies how features are rendered. Each feature must have - a `geometry` column (type based on the OGC's Simple Feature Access Model (SFA), excluding support for `GeometryCollection` types) - an optional `id` column - optional property columns While geometries are not restricted to a single type, using one type per table is recommended for efficiency. As in MVT, geometry coordinates are encoded as integers within vector tile grid coordinates. !!! NOTE The terms `column`, `field`, and `property` are used interchangeably in this document. # Tile Layout A `FeatureTable` in the MLT specification uses a tabular, column-oriented layout. It employs various lightweight compression schemes to encode column values efficiently. A FeatureTable consists of a mandatory `geometry` column, an optional `id` column, and optional property columns. The absence of a single header at the beginning of the tile allows `FeatureTable`s to be constructed independently, and even concatenated on the fly. A logical column is separated into several physical `streams` (sub-columns), inspired by the ORC file format. These streams are stored contiguously. A stream is a sequence of values of a known length in a continuous memory chunk, all sharing the same type. Streams include additional metadata, such as their size and encoding type. For example, a nullable string property column might have: - A **`present` stream** (a bit flag indicating the presence of a value). - A **`length` stream** (describing the number of characters for each string). - A **`data` stream** (containing the actual UTF-8 encoded string values). MLT defines the following stream types: - **Present**: Enables efficient encoding of sparse columns by indicating value presence via a bit flag. This stream can be omitted if the column is not nullable (as declared in the `FieldMetadata`). - **Data**: Stores the actual column data (e.g., `boolean`, `int`, `float`, or `string` values for feature properties, dictionary-encoded values, or geometry coordinates). For fixed-size data types (`boolean`, `int`, `float`), this is the only required stream besides the optional `present` stream. - **Length**: Specifies the number of elements for variable-sized data types like strings or lists. - **Offset**: Stores offsets into a data stream when using dictionary encoding (e.g., for strings or vertices). These physical streams are further categorized into logical streams that define how to interpret the data: ```mermaid classDiagram class DictionaryLogicalStreamType { <> Single Shared Vertex Morton FSST } class OffsetLogicalStreamType { <> Vertex Index String Key } class LengthLogicalStreamType { <> Geometries Parts Rings Triangles Symbol Dictionary } ``` ## Metadata ### Tileset Metadata !!! NOTE Tileset metadata was initially implemented as a size reduction experiment. This feature is not currently supported. Global metadata for the entire tileset is stored separately in a [JSON file](assets/spec/mlt_tileset_metadata.json). This tileset metadata provides information for the full tileset and is the equivalent of the TileJSON spec commonly used with MVT and other tile types. By defining this information once per tileset, we avoid redundant metadata in each tile, saving significant space, especially for small tiles. ### Tile Metadata There is no global tile header. Each `FeatureTable` has its own metadata. ### FeatureTable Metadata Each `FeatureTable` is preceded by a `FeatureTableMetadata` section describing it. !!! CAUTION This is not clear, and possibly incorrect. Why any number? Should the size of the upcoming metadata and table be part of that structure? A FeatureTable consists of any number of the following sequences: - The size of the upcoming `FeatureTableMetadata` (varint-encoded). - The size of the upcoming `FeatureTable` (varint-encoded). - One `FeatureTableMetadata` section. - One `FeatureTable` section. This structure allows a tile to be built by simply concatenating separate results. The `FeatureTableMetadata` is described in detail below. Within a `FeatureTable`, additional metadata describes the structure of each part: - **FieldMetadata**: Contains information about a field (column), including the number of streams it comprises and its vector type for efficient decoding into the in-memory format. Every field section is preceded by a `FieldMetadata` section. - **StreamMetadata**: Contains information about a stream, such as the encoding scheme used and the number of values. Every stream section is preceded by a `StreamMetadata` section. Since every `Field` has a `FieldMetadata` section, even for fields absent in a specific tile, no `id` is needed. A field's absence is indicated by a zero value for its number of streams. All integers in metadata sections are `Varint`-encoded (for u32) or bit-packed (for u8). ```mermaid --- config: class: hideEmptyMembersBox: true title: FeatureTableSchema --- classDiagram direction TB class Tile { +LayerGroup[] groups } class LayerGroup { +VarInt metadataSize +TileMetadata metadata +u8[] tileData } class TileMetadata { +FeatureTable[] featureTables } class FeatureTable { +String name +VarInt columnCount +Column[] columns } class Column { +ColumnOptions options %% VarInt +String name +ScalarColumn scalarType %% oneof i.e., scalarType XOR complexType +ComplexColumn complexType } Tile --> LayerGroup : groups LayerGroup --> TileMetadata : metadata TileMetadata --> FeatureTable : featureTables FeatureTable --> Column : columns ``` Strings are encoded as UTF-8 sequences of characters with a length header: ```mermaid --- config: class: hideEmptyMembersBox: true title: StringsSchema --- classDiagram direction TB class String { +VarInt length +u8 bytes[length] %% encoding is always UTF-8 } ``` Columns can be thought of as a union of scalar and complex columns and can have these options: ```mermaid --- config: class: hideEmptyMembersBox: true title: ColumnsSchema --- classDiagram direction TB class FieldOptions { NULLABLE = 1, %% Property is nullable COMPLEX_TYPE = 2, %% A complexType follows if set, else a scalarType [EXPERIMENTAL] LOGICAL_TYPE = 4, %% A logical type follows if set, else a physical type [EXPERIMENTAL] CHILD_TYPES = 8, %% 1: Child types are present [EXPERIMENTAL] } class ColumnOptions { VERTEX_SCOPE = 16, %% Property is vertex-scope if set, else feature-scope } <> FieldOptions <> ColumnOptions FieldOptions <|-- ColumnOptions ``` Scalar columns are laid out as follows: ```mermaid --- config: class: hideEmptyMembersBox: true title: ScalarColumnsSchema --- classDiagram direction TB class Column { } class ScalarColumn { +ScalarColumnOptions options %% VarInt +ScalarType physicalType %% oneof i.e., physicalType XOR logicalType +LogicalScalarType logicalType } class ScalarType { BOOLEAN = 0 INT_8 = 1 UINT_8 = 2 INT_32 = 3 UINT_32 = 4 INT_64 = 5 UINT_64 = 6 FLOAT = 7 DOUBLE = 8 STRING = 9 INT_128 = 10 UINT_128 = 11 } class LogicalScalarType { TIMESTAMP = 0 DATE = 1 JSON = 2 } <> ScalarType <> LogicalScalarType note for LogicalScalarType "[EXPERIMENTAL]" Column --> ScalarColumn : scalarType ScalarColumn --> ScalarType : physicalType ScalarColumn --> LogicalScalarType : logicalType ``` And `ComplexColumns` like this: ```mermaid --- title: ComplexColumnsSchema config: class: hideEmptyMembersBox: true --- classDiagram direction TB class ComplexColumn { +ComplexType physicalType %% oneof i.e., physicalType XOR logicalType +LogicalComplexType logicalType +VarInt childCount %% Present only if CHILD_TYPES is set in columnOptions +Field[] children } class Field { +FieldOptions options %% VarInt +String name +ScalarField scalarField %% oneof i.e., scalarField XOR complexField +ComplexField complexField } class ScalarField { +ScalarType physicalType %% oneof i.e., physicalType XOR logicalType +LogicalScalarType logicalType } class ComplexField { +ComplexType physicalType %% oneof i.e., physicalType XOR logicalType +LogicalComplexType logicalType +VarInt childCount %% Present only if CHILD_TYPES is set in columnOptions +Field[] children } class ComplexType { VEC_2 = 0 VEC_3 = 1 GEOMETRY = 2 GEOMETRY_Z = 3 LIST = 4 MAP = 5 STRUCT = 6 } class LogicalComplexType { BINARY = 0 RANGE_MAP = 1 } class Column { } <> ComplexType <> LogicalComplexType note for ComplexColumn "[EXPERIMENTAL]" note for ComplexField "[EXPERIMENTAL]" note for ComplexType "[EXPERIMENTAL]" note for LogicalComplexType "[EXPERIMENTAL]" note for Field "[EXPERIMENTAL]" note for ScalarField "[EXPERIMENTAL]" Column --> ComplexColumn : complexType ComplexColumn --> Field : children ComplexField --> Field : children Field --> ComplexField : complexField Field --> ScalarField : scalarField ``` ## Type System The MLT type system distinguishes between physical and logical types. Physical types define the data layout in storage, while logical types add semantic meaning. This separation simplifies encoder and decoder implementation and allows encoding schemes to be reused. ### Physical Types Physical types define the data layout in storage. Both scalar and complex types can be categorized as fixed-size or variable-size binaries. Variable-size binaries require an additional length stream to specify the size of each element. Fixed-size binaries have a consistent bit (boolean) or byte width and thus require no length stream. **Scalar Types** Each scalar type uses a specific encoding scheme for its data stream. | Data Type | Logical Types | Description | Layout | |-------------------------------------------|---------------------------------|--------------------------------------|---------------| | Boolean | | | Fixed-Size | | Int8, UInt8, Int32, UInt32, Int64, UInt64 | Date (int32), Timestamp (int64) | | Fixed-Size | | Float, Double | | | Fixed-Size | | String | JSON | UTF-8 encoded sequence of characters | Variable-Size | **Complex Types ** Complex types are composed of scalar types. | Data Type | Logical Types | Description | Layout | |------------------|----------------------|----------------------------------------------------|---------------| | List | Binary (List) | | Variable-Size | | Map | Map | Additional key stream -> length, key, data streams | Variable-Size | | Struct | | | | | Vec2, Vec3 | Geometry, GeometryZ | | Fixed-Size | ### Logical Types !!! CAUTION Original text had `encodings can be reused` text which is unclear. What is "encodings" in this context? Logical types add semantics on top of physical types, enabling code reuse and simplifying encoder/decoder implementation. | Logical Type | Physical Type | Description | |--------------|----------------------|--------------------------------------------| | Date | Int32 | Number of days since Unix epoch | | Timestamp | Int64 | Number of milliseconds since Unix epoch | | RangeMap | Map, T> | For storing linear referencing information | | Binary | List | | | JSON | String | | | Geometry | vec2 | | | GeometryZ | vec3 | | ### Nested Fields Encoding For nested properties (e.g., structs, lists), a [present/length](https://arxiv.org/pdf/2304.05028.pdf) pair encoding is chosen over the Dremel encoding for its simpler implementation and faster decoding into the in-memory format. Every nullable field has an additional `present` stream. Every collection type field (e.g., a list) has an additional `length` stream specifying its length. As in ORC, nested fields are flattened based on a pre-order traversal. Nested fields can also use shared dictionary encoding to share a common dictionary (e.g., for localized `name:*` columns in an OSM dataset). Fields using a shared dictionary must be grouped sequentially in the file and prefixed by the dictionary. ### RangeMap `RangeMaps` efficiently encode linear referencing information, as used in [Overture Maps](https://docs.overturemaps.org/overview/feature-model/scoping-rules#geometric-scoping-linear-referencing). `RangeSets` store range values and data values in two separate streams. The min and max values for the ranges are stored as interleaved double values in a separate range stream. ## Encoding Schemes MLT uses various lightweight compression schemes for space-efficient storage and fast decoding. Encodings can be recursively cascaded (hybrid encodings) to a certain degree. For example, integer columns resulting from dictionary encoding can be further compressed using integer encoding schemes. The following encoding pool was selected based on analysis of compression ratio and decoding speed on test datasets like OpenMapTiles and Bing Maps tilesets. | Data Type | Logical Level Technique | Physical Level Technique | Description | | --------- | ---------------------------- | ----------------------------------- | ----------- | | Boolean | [Boolean RLE](https://orc.apache.org/specification/ORCv1/#boolean-run-length-encoding) | | | | Integer | Plain, RLE, Delta, Delta-RLE | [SIMD-FastPFOR](https://arxiv.org/pdf/1209.2137.pdf), [Varint](https://protobuf.dev/programming-guides/encoding/#varints) | | | Float | Plain, RLE, Dictionary | | | | String | Plain, Dictionary, [FSST](https://www.vldb.org/pvldb/vol13/p2649-boncz.pdf) Dictionary | | | | Geometry | Plain, Dictionary, Morton-Dictionary | | | !!! NOTE `FSST`, and `FastPFOR` encodings are . SIMD-FastPFOR is generally preferred over Varint encoding due to its smaller output and faster decoding speed. Varint encoding is included mainly for compatibility and simplicity, and it can be more efficient when combined with heavyweight compression like GZip. A brute-force search for the best encoding scheme is too costly. Instead, we recommend the selection strategy from the [BTRBlocks](https://www.cs.cit.tum.de/fileadmin/w00cfj/dis/papers/btrblocks.pdf) paper: - Calculate data metrics to exclude unsuitable encodings early (e.g., exclude RLE if the average run length is less than 2). - Use a sampling-based algorithm: randomly select parts of the data totaling ~1% of the full dataset and apply the candidate encodings from step 1. Choose the scheme that produces the smallest output. ## FeatureTable Layout ### ID Column An `id` column is not mandatory. If included, it should be a u64 or narrower integer type (u32 if possible) for MVT compatibility. A narrower type enables the use of efficient encodings like FastPfor128. ### Geometry Column The geometry column uses a Structure of Arrays (SoA) layout (data-oriented design). The `x`, `y`, and optional `z` coordinates are stored interleaved in a `VertexBuffer` for efficient CPU processing and direct copying to GPU buffers. If the `z` coordinate is not needed for rendering, it can be stored separately as an `M-value` (see vertex-scoped properties). The geometry information is separated into different streams, partly inspired by the [geoarrow](https://github.com/geoarrow/geoarrow) specification. This separation enables better compression optimization and faster processing. Pre-tessellated polygon meshes can also be stored directly to avoid runtime triangulation. A geometry column can consist of the following streams: | Stream Name | Data Type | Encoding | Mandatory | | -------------- | :-------: | ------------------ | :-------: | | GeometryType | Byte | Integer | ✓ | | NumGeometries | UInt32 | Integer | | | NumParts | UInt32 | Integer | | | NumRings | UInt32 | Integer | | | NumTriangles | UInt32 | Integer | | | IndexBuffer | UInt32 | Integer | | | VertexOffsets | UInt32 | Integer | | | VertexBuffer | Int32 or Vertex[] | Plain, Dictionary, Morton | ✓ | Depending on the geometry type, the following streams are used in addition to `GeometryType`: - **Point**: VertexBuffer - **LineString**: NumParts, VertexBuffer - **Polygon**: NumParts (Polygon), NumRings (LinearRing), VertexBuffer - **MultiPoint**: NumGeometries, VertexBuffer - **MultiLineString**: NumGeometries, NumParts (LineString), VertexBuffer - **MultiPolygon**: NumGeometries, NumParts (Polygon), NumRings (LinearRing), VertexBuffer An additional `VertexOffsets` stream is present when using Dictionary or Morton-Dictionary encoding. If geometries (mainly polygons) are pre-tessellated for direct GPU use, `NumTriangles` and `IndexBuffer` streams must be provided. ### Property Columns Feature properties are divided into `feature-scoped` and `vertex-scoped` properties. - **Feature-scoped**: One value per feature. - **Vertex-scoped**: One value per vertex in the VertexBuffer per feature (modeling M-coordinates from GIS). !!! TODO Would it make sense to place vertex-scoped properties AFTER feature scoped ones? I suspect some implementations may act of feature-scoped properties first, and possibly even ignore vertex-scoped, at least for now (esp since vertex-scoped ones are still experimental) Vertex-scoped properties must be grouped together and placed before feature-scoped properties in the FeatureTable. A property's scope is defined in the tileset metadata using the `ColumnScope` enum. A property column can use any data type from the [type system](#type-system). # Example Layouts The following examples illustrate the layout of a `FeatureTable` in storage. The color scheme is: - **Blue boxes**: Logical constructs, not persisted. Fields are reconstructed from streams based on TileSet metadata. - **White boxes**: Metadata describing data structure (FeatureTable, Stream (SM), Feature (FM) metadata). - **Yellow boxes**: Streams containing the actual data. ## Place Layer Given a place [layer](assets/spec/place_feature.json) with the following JSON schema structure: ![](assets/spec/place_feature.png) The resulting MLT tile layout for this layer, using a dictionary for the `geometry` and `name` columns, might look like this: ![img_2.png](assets/spec/img_2.png) ## LineString Geometry with Flat Properties Encoding of a `FeatureTable` with an `id` field, a `LineString` geometry field, and the flat feature-scoped properties `class` and `subclass`: ![img_4.png](assets/spec/img_4.png) ## MultiPolygon with Flat Properties Encoding of a `FeatureTable` with an `id` field, a `MultiPolygon` geometry field, and flat feature-scoped property fields. A `VertexOffsets` stream is present due to vertex dictionary encoding: ![img_5.png](assets/spec/img_5.png) ## Vertex-Scoped and Feature-Scoped Properties Example layout encoding vertex-scoped and feature-scoped properties. All vertex-scoped properties are grouped together and placed before feature-scoped properties. The `id` column is not nullable, so its present stream is omitted. ![img_7.png](assets/spec/img_7.png) # Sorting Choosing the right column to sort features by can significantly reduce the size of the `FeatureTable`. Sorting is crucial for leveraging the columnar layout fully. Exhaustively testing every possible sorting order for every column in every layer is computationally expensive. See recommended heuristic in the [encoding schemes](#encoding-schemes). # Encodings !!! TODO inline encodings here Encoding details are specified in [a separate document](encodings.md). # In-Memory Format !!! NOTE The following is a high-level overview; the in-memory format will be explained in more detail later. The record-oriented, array-of-structures in-memory model used by libraries processing Mapbox Vector Tiles incurs considerable overhead. This includes creating many small objects (increasing memory allocation load) and placing additional strain on garbage collectors in browsers. MLT uses a columnar memory layout (data-oriented design) for its in-memory format to overcome these issues. This approach improves cache utilization for subsequent data access and enables the use of fast SIMD instructions. The MLT in-memory format incorporates ideas from analytical in-memory formats like Apache Arrow, Velox, and the DuckDB execution format, tailored for visualization use cases. It is also designed for future parallel processing on the GPU within compute shaders. The main design goals for the MLT in-memory format are: - Define a platform-agnostic representation to avoid expensive materialization costs, especially for strings. - Maximize CPU throughput by optimizing memory layout for cache locality and SIMD instructions. - Allow random (preferably constant-time) access to all data for parallel processing on GPUs (compute shaders). - Provide compressed data structures that can be processed directly without full decoding. - Provide tile geometries in a representation that can be loaded into GPU buffers with minimal additional processing. Data is stored in contiguous memory buffers called **vectors**, accompanied by metadata and an optional null bitmap. The storage format includes a `VectorType` field in the metadata to instruct the decoder which vector type to use for a specific field. An auxiliary offset buffer enables random access to variable-sized data types like strings or lists. The MLT in-memory format supports the following vector types: - [Flat Vectors](https://duckdb.org/internals/vector.html#flat-vectors) - [Constant Vectors](https://duckdb.org/internals/vector.html#constant-vectors) - [Sequence Vectors](https://duckdb.org/internals/vector.html#sequence-vectors) - [Dictionary Vectors](https://duckdb.org/internals/vector.html#dictionary-vectors) - FSST Dictionary Vectors - Shared Dictionary Vectors - [Run-End Encoded (REE) Vectors](https://arrow.apache.org/docs/format/Columnar.html#run-end-encoded-layout) !!! NOTE Further evaluation is needed to determine if [recent research](https://arxiv.org/pdf/2306.15374.pdf) can enable random access on delta-encoded values. Using a compressed vector where possible makes the conversion from storage to in-memory format essentially a zero-copy operation. Following Apache Arrow's approach and the [Intel performance guide](https://www.intel.com/content/www/us/en/developer/topic-technology/data-center/overview.html), decoders should allocate memory on addresses aligned to a 64-byte multiple (where possible). ================================================ FILE: java/.gitignore ================================================ .gradle/ build/ output/ *.mlt.mbtiles *.mlt.pmtiles ================================================ FILE: java/CONTRIBUTING.md ================================================ To build the project run the following command: ````bash ./gradlew build ```` To format the code run the following command: ````bash ./gradlew spotlessApply ```` To execute the benchmarks run the following command: ````bash ./gradlew jmh ```` To build just the CLI tools: ````bash ./gradlew cli ```` To run the tests: ````bash ./gradlew test ```` To run specific tests like the `MltDecoderTest` test: ````bash ./gradlew test --tests com.mlt.decoder.MltDecoderTest ```` View test reports by opening `build/reports/tests/test/index.html` View test coverage reports by opening `build/reports/jacoco/test/html/index.html` ================================================ FILE: java/README-Decode.md ================================================ ## MLT Decoder Java CLI Application This is a very simple application which serves as an example of how to use the MLT library to decode MLT files. It can also be used to benchmark the performance of the MLT decoder by timing the repeated decoding of a single tile. ================================================ FILE: java/README-Encode.md ================================================ ## MLT Encoder Java CLI Application This application currently only supports converting MVT tiles to MLT format. It can convert standalone tile files, MBTiles files, MLN Offline database files, and PMTiles files. ### Usage See the `--help` output for the full list of options. At a minimum, the input file(s) must be specified with `--mvt`, `--mbtiles`, `--offlinedb`, or `--pmtiles`. In the case of `--pmtiles`, the input may be a URL, in which case the data will be selectively downloaded, allowing rapid conversion of zoom-level subsets. By default, the output file will be the input basename with the `.mlt` extension added, placed in the working directory. This can be overridden with the `--mlt` option. The `--dir` option changes the output directory. For efficient conversion the `--parallel` option should generally be used to take advantage of all available CPU cores. ### Environment Variables #### Common - `MLT_TILE_LOG_INTERVAL`: Controls the number of tiles between progress reports with `--verbose` #### MBTiles Tile compression within an MBTiles file is optional. The encoder will only store compressed MLT tiles if they are slightly smaller than the uncompressed MLT data. To be stored compressed, a tile must meet both criteria, as controlled by the following environment variables: - `MLT_COMPRESSION_RATIO_THRESHOLD`: Minimum compression ratio to apply compression (default: 0.98) - If compressed tile is larger than this fraction of the uncompressed tile, it will be discarded and the uncompressed tile will be stored instead. - This value may be greater than 1.0 to indicate that tiles should be stored in compressed form even if it increases their size. - `MLT_COMPRESSION_FIXED_THRESHOLD`: Minimum savings in bytes for a tile to be compressed (default: 20) - If the compressed tile size plus this value is greater than the uncompressed tile size, the compressed tile will be discarded and the uncompressed tile will be stored instead. - This value can be negative to indicate that tiles should be stored in compressed form even if they are larger than the uncompressed tile. #### PMTiles PMTiles files can be extremely large, and may benefit from careful selection of extended configuration parameters. PMTiles conversion can use substantial memory, especially for large archives, high cache sizes, and many parallel worker threads. - `MLT_CACHE_MAX`: Maximum cache size in bytes. - `MLT_CACHE_MAX_HEAP_PERCENT`: Maximum cache size as a percentage of maximum heap size. - If a percentage is specified, it will take precedence over `MLT_CACHE_MAX`. - `MLT_CACHE_EXPIRE`: Cache expiration duration after access, in ISO-8601 (e.g. P1.2S) or plain (e.g., 1.2s). - This is generally not important, but may decrease memory use with a very large cache in long-running conversions. - `MLT_CACHE_BLOCK_SIZE`: Aligned block size of cached data, in bytes - Cache misses will trigger a read of the entire block(s) containing the tile, often allowing reads of nearby tiles without an additional request. - When zero, only the requested tile will be read on a cache miss. This is not recommended. - Larger block sizes mean fewer entries in the cache, but tend to be more efficient as the main benefit is from other tiles in the same block. - `MLT_CACHE_AVERAGE_SIZE`: Average size of tiles in bytes - This is used to estimate the number of tiles that can be stored in the cache. - A value of zero disables cache pre-allocation. - `MLT_MAX_TILE_TRACK_SIZE`: Maximum size of a tile to be tracked in memory, in bytes. - PMTiles files combine ranges of identical tiles (in Hilbert index order) into a single directory entry. If the same tile contents appears elsewhere, however, it will be stored as a separate entry. The encoder keeps track of each input tile in order to avoid re-converting it if it's encountered later, reducing the computation needed at the cost of some memory. - Only small tiles, e.g., representing empty ocean, are commonly duplicated and so worth tracking. So to save memory, only tiles below this threshold are tracked. - Set to zero to disable tracking altogether. - `MLT_THREAD_QUEUE_SIZE`: The maximum number of items (per worker thread) to queue when parallel processing is enabled. - Larger values cause the directory traversal to finish earlier allowing for a progress percentage to be displayed, at the cost of more memory use. - Very small values may lead to queue starvation and reduced performance. In addition, it may be beneficial to adjust the JVM heap parameters to allow for a heap size larger than the default, e.g.: - `-Xmx32G` to allow up to 32 GB of heap memory - `-Xms16G` to pre-allocate 16 GB of heap memory at startup Cache hits are more important when using a remote source, but a very large cache (1GB+) seems to be counterproductive. Use `--cache-stats` to print cache statistics to help tune the cache configuration parameters. ================================================ FILE: java/README.MD ================================================ # MapLibre Tile Java [![Maven Central Version](https://img.shields.io/maven-central/v/org.maplibre/mlt)](https://central.sonatype.com/artifact/org.maplibre/mlt) ![GitHub License](https://img.shields.io/github/license/maplibre/maplibre-tile-spec) [![test](https://github.com/maplibre/maplibre-tile-spec/actions/workflows/test.yml/badge.svg)](https://github.com/maplibre/maplibre-tile-spec/actions/workflows/test.yml) A Java library for encoding and decoding MapLibre Tile (MLT) format, providing efficient compression and fast access to vector tile data. ## Project Structure This is a multi-project Gradle build with two main components: - **`mlt-core`** - The core library containing the MLT encoding/decoding functionality - **`mlt-cli`** - Command-line tools for converting between MVT and MLT formats - **`mlt-tools`** - Utilities for working with MLT, currently only used for testing The core library is published to Maven Central, while the CLI tools are built locally for development use. ## Installation ### Gradle (`build.gradle`) ```gradle repositories { mavenCentral() maven { url 'https://maven.ecc.no/releases' } } dependencies { implementation 'org.maplibre:mlt:0.0.10' } ``` ### Gradle (`build.gradle.kts`) ```kotlin repositories { mavenCentral() maven { url = uri("https://maven.ecc.no/releases") } } dependencies { implementation("org.maplibre:mlt:0.0.10") } ``` ### Maven ```xml maven-central https://repo1.maven.org/maven2 ecc-releases https://maven.ecc.no/releases org.maplibre mlt 0.0.10 ``` See the [Maven Central Repository](https://mvnrepository.com/artifact/org.maplibre/mlt) for the latest published versions and syntax for other build systems. ## Usage Examples ### Basic MLT Encoding Convert a Mapbox Vector Tile (MVT) to MLT format: ```java import org.maplibre.mlt.converter.MltConverter; import org.maplibre.mlt.converter.mvt.MvtUtils; import org.maplibre.mlt.converter.ColumnMapping; import org.maplibre.mlt.converter.ConversionConfig; import org.maplibre.mlt.converter.FeatureTableOptimizations; import org.maplibre.mlt.metadata.tileset.MltMetadata; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Map; byte[] mvtData = Files.readAllBytes(Path.of("tile.mvt")); var mvtTile = MvtUtils.decodeMvt(mvtData); // Configure column mappings for nested properties (e.g., name:en, name:de) var columnMappingConfig = List.of(new ColumnMapping("name", ":", true)); var tilesetMetadata = MltConverter.createTilesetMetadata(mvtTile, columnMappingConfig, true); var optimization = new FeatureTableOptimizations(false, false, columnMappingConfig); var optimizations = Map.of("layer_name", optimization); var config = new ConversionConfig(true, true, optimizations); byte[] mltData = MltConverter.convertMvt(mvtTile, tilesetMetadata, config, null); Files.write(Path.of("tile.mlt"), mltData); ``` ### Basic MLT Decoding The simplest way to decode an MLT tile: ```java import org.maplibre.mlt.decoder.MltDecoder; import org.maplibre.mlt.data.MapLibreTile; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; byte[] mltData = Files.readAllBytes(Path.of("tile.mlt")); MapLibreTile tile = MltDecoder.decodeMlTile(mltData); for (var layer : tile.layers()) { System.out.println("Layer: " + layer.name()); System.out.println("Features: " + layer.features().size()); for (var feature : layer.features()) { System.out.println("Feature ID: " + feature.id()); System.out.println("Geometry: " + feature.geometry().getGeometryType()); System.out.println("Properties: " + feature.properties()); } } ``` ### Advanced Encoding with Optimizations For better compression, you can enable advanced encoding schemes and sorting: ```java import org.maplibre.mlt.converter.FeatureTableOptimizations; import java.util.Map; var optimization = new FeatureTableOptimizations(true, true, columnMappingConfig); var optimizations = Map.of( "water", optimization, "roads", optimization, "buildings", optimization ); var config = new ConversionConfig(true, true, optimizations); byte[] mltData = MltConverter.convertMvt(mvtTile, tilesetMetadata, config, null); ``` ### Working with Feature Properties Access and manipulate feature properties: ```java import org.maplibre.mlt.decoder.MltDecoder; import org.maplibre.mlt.data.MapLibreTile; MapLibreTile tile = MltDecoder.decodeMlTile(mltData); for (var layer : tile.layers()) { for (var feature : layer.features()) { var properties = feature.properties(); String name = (String) properties.get("name"); Integer population = (Integer) properties.get("population"); Boolean isCapital = (Boolean) properties.get("is_capital"); // Work with nested properties (if using column mappings) String nameEn = (String) properties.get("name:en"); String nameDe = (String) properties.get("name:de"); properties.put("processed", true); } } ``` ### Batch Processing Process multiple tiles efficiently: ```java import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.stream.Stream; import org.maplibre.mlt.converter.MltConverter; import org.maplibre.mlt.converter.mvt.MvtUtils; import org.maplibre.mlt.converter.ConversionConfig; import org.maplibre.mlt.metadata.tileset.MltMetadata; import java.io.IOException; import java.util.List; import java.util.Map; try (Stream paths = Files.list(Paths.get("input_tiles"))) { paths.filter(path -> path.toString().endsWith(".mvt")) .forEach(mvtPath -> { try { byte[] mvtData = Files.readAllBytes(mvtPath); var mvtTile = MvtUtils.decodeMvt(mvtData); var tilesetMetadata = MltConverter.createTilesetMetadata(mvtTile, List.of(), true); var config = new ConversionConfig(true, true, Map.of()); byte[] mltData = MltConverter.convertMvt(mvtTile, tilesetMetadata, config, null); String mltPath = mvtPath.toString().replace(".mvt", ".mlt"); Files.write(Paths.get(mltPath), mltData); System.out.println("Converted: " + mvtPath + " -> " + mltPath); } catch (IOException e) { System.err.println("Error processing " + mvtPath + ": " + e.getMessage()); } }); } ``` ## CLI Usage To build command line tools: ```console ./gradlew cli ``` This will create two executable JAR files in `mlt-cli/build/libs/`: - `encode.jar` - Convert MVT files to MLT format - `decode.jar` - Decode MLT files Example of converting MVT to MLT: ```console java -jar mlt-cli/build/libs/encode.jar --mvt ../test/fixtures/simple/point-boolean.pbf --mlt /tmp/point-boolean.mlt --tessellate --outlines ALL --verbose java -jar mlt-cli/build/libs/encode.jar --mbtiles /path/to/input.mbtiles --mlt /tmp/output.mbtiles --tessellate --outlines ALL --compress=deflate --verbose java -jar mlt-cli/build/libs/encode.jar --pmtiles /path/to/input.pmtiles --mlt /tmp/output.pmtiles --tessellate --outlines ALL --parallel ``` Example of decoding MLT files: ```console java -jar mlt-cli/build/libs/decode.jar --mlt /tmp/point-boolean.mlt ``` For more details, see [README-Encode.md](README-Encode.md) and [README-Decode.md](README-Decode.md). ## Contributing Run the tests: ``` just java::test just java::test-cli ``` ================================================ FILE: java/encoding-server/.gitignore ================================================ cache/ node_modules/ ================================================ FILE: java/encoding-server/README.md ================================================ # MVT to MLT Development Server This Node.js-based application serves as a **development and testing server** for converting **MVT** to **MLT** in realtime. The server acts as a proxy for style, source, and tile endpoints, enabling seamless on-demand transformation of vector tile data. > Most of the arguments supported by the java encoder are also available as Node.js arguments or through `config.json` file. --- ## Prerequisites - Node.js - npm (Node package manager) - Java MLT encoder ## Running the Server ```bash cd encoding-server npm install npm start ``` ## Requests In most cases, the initial style request is sufficient to initiate the process, with all subsequent source and tile requests automatically redirected back to the server. ```bash ## style - http:///style?url= curl http://0.0.0.0/style?url=https://demotiles.maplibre.org/style.json ## default curl http://localhost/style?url=https://demotiles.maplibre.org/style.json curl http://10.0.2.2/style?url=https://demotiles.maplibre.org/style.json ## Android emulator bridge to 0.0.0.0 ## source - http:///style?url= curl http://0.0.0.0/source?url=https://demotiles.maplibre.org/tiles/tiles.json ## tile - http:///tile?url= curl http://0.0.0.0/tile?url=https://demotiles.maplibre.org/tiles/{z}/{x}/{y}.pbf ``` ================================================ FILE: java/encoding-server/config.json ================================================ { "host": "0.0.0.0", "port": 80, "verbose": true, "input": "mvt", "noids": false, "fsst": false, "fastpfor": false, "nomorton": false, "outlines": "ALL", "tessellate": false, "coercemismatch": true, "timer": false, "compare": false } ================================================ FILE: java/encoding-server/config.mjs ================================================ import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { parseArgs } from "node:util"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const configFile = JSON.parse( fs.readFileSync(path.join(__dirname, "config.json")), ); const { values: args } = parseArgs({ options: { host: { type: "string", default: "0.0.0.0" }, port: { type: "string", default: "80" }, verbose: { type: "boolean", default: true }, keep_files: { type: "boolean", default: false }, noencodingserver: { type: "boolean", default: false }, input: { type: "string", default: "mvt" }, noids: { type: "boolean", default: false }, fsst: { type: "boolean", default: false }, fastpfor: { type: "boolean", default: false }, nomorton: { type: "boolean", default: false }, tessellate: { type: "boolean", default: false }, outlines: { type: "string", default: "ALL" }, coercemismatch: { type: "boolean", default: true }, timer: { type: "boolean", default: false }, compare: { type: "boolean", default: false }, }, args: process.argv.slice(2), }); const possibleInputs = ["mvt", "pmtiles"]; if (!possibleInputs.includes(args.input)) { console.error(`--input must be one of ${possibleInputs.join(", ")}`); process.exit(1); } const config = { ...args, ...configFile, port: Number(configFile.port ?? args.port ?? 80), }; config.cachePath = path.join(__dirname, "cache"); config.cliToolsPath = path.join(__dirname, "../"); config.encoderPath = path.join( config.cliToolsPath, "mlt-cli/build/libs/encode.jar", ); config.encoderPort = 3001; export default config; ================================================ FILE: java/encoding-server/convert.mjs ================================================ import { exec, execSync, spawn } from "node:child_process"; import { randomUUID } from "node:crypto"; import { createWriteStream, existsSync, mkdirSync, unlink } from "node:fs"; import net from "node:net"; import { join } from "node:path"; import { Readable } from "node:stream"; import { pipeline } from "node:stream/promises"; import config from "./config.mjs"; function convertRequest(convertResponse) { return (req, res) => { if (config.verbose) { console.log(req.originalUrl); } if (!req.query.url) { if (config.verbose) { console.error("Missing `url` parameter"); } res.status(400).send("Missing `url` parameter"); return; } let url; try { url = new URL(req.query.url); } catch { if (config.verbose) { console.error(`Invalid url ${req.query.url}`); } res.status(400).send(`Invalid url: ${req.query.url}`); return; } fetch(url) .then((styleResponse) => styleResponse.text()) .then((data) => convertResponse(req, data, res)) .catch((error) => { if (config.verbose) { console.error(`Request failed: ${req.query.url} - ${error}`); } res.status(500).send(`Request failed: ${req.query.url} - ${error}`); }); }; } function convertURL(urlString, type, req) { try { const url = new URL(urlString); if (url.protocol !== "https:" && url.protocol !== "http:") { return urlString; } } catch (error) { if (config.verbose) { console.error(`URL (${urlString}) parse error: ${error}`); } return urlString; } return `${req.protocol}://${req.get("host")}/${type}?url=${urlString}`; } function convertStyleResponse(req, data, res) { try { const json = JSON.parse(data); if (json.sources) { for (const key in json.sources) { if (!Object.hasOwn(json.sources, key)) { continue; } const source = json.sources[key]; if (!source || source.type !== "vector") { continue; } source.encoding = "mlt"; if (source.url) { source.url = convertURL(source.url, "source", req); } if (source.tiles) { source.tiles = source.tiles.map((tile) => { return convertURL(tile, "tile", req); }); } } } res.status(200).json(json); } catch (error) { if (config.verbose) { console.error(`Failed to parse style response: ${error}`); } res.status(400).send(`Failed to parse style response: ${error}`); } } function convertSourceResponse(req, data, res) { try { const json = JSON.parse(data); json.encoding = "mlt"; if (json.tiles) { for (const key in json.tiles) { if (!Object.hasOwn(json.tiles, key)) { continue; } json.tiles[key] = convertURL(json.tiles[key], "tile", req); } } res.status(200).json(json); } catch (error) { if (config.verbose) { console.error(`Failed to parse style response: ${error}`); } res.status(400).send(`Failed to parse style response: ${error}`); } } const convertStyleRequest = convertRequest(convertStyleResponse); const convertSourceRequest = convertRequest(convertSourceResponse); function convertTileResponse(filePath, res) { const mltPath = `${filePath}.mlt`; const args = " --" + config.input + " " + filePath + " --mlt " + mltPath + (config.noids ? " --noids" : "") + (config.fsst ? " --fsst" : "") + (config.fastpfor ? " --fastpfor" : "") + (config.nomorton ? " --nomorton" : "") + (config.outlines ? ` --outlines ${config.outlines}` : "") + (config.tessellate ? " --tessellate" : "") + (config.coercemismatch ? " --coerce-mismatch" : "") + (config.timer ? " --timer" : "") + (config.compare ? " --compare-all" : ""); const callback = (error, stdout, stderr) => { if (config.verbose) { if (stdout) { console.log(`Encoder output: ${stderr}`); } if (stderr) { console.error(`Encoder error: ${stderr}`); } } if (!config.keep_files) { unlink(filePath, (fileErr) => { if (fileErr && config.verbose) { console.error( `Failed to delete input file: ${filePath} - ${fileErr}`, ); } }); } if (error) { if (config.verbose) { console.error(`Tile encoding failed: ${error}`); } res.status(500).send(`Tile encoding failed: ${error}`); return; } res.on("finish", () => { if (!config.keep_files) { unlink(mltPath, (fileErr) => { if (fileErr && config.verbose) { console.error( `Failed to delete output file: ${mltPath} - ${fileErr}`, ); } }); } }); res.status(200).sendFile(mltPath); }; if (config.noencodingserver) { convertTileCLI(args, callback); } else { convertTileCLIServer(args, callback); } } function convertTileCLI(args, callback) { const command = `java -jar ${config.encoderPath} ${args}`; exec(command, callback); } function convertTileCLIServer(args, callback) { const command = `${args}\n`; let response = ""; const socket = new net.Socket(); socket.connect(config.encoderPort, "localhost", () => { socket.write(command); }); socket.on("data", (data) => { response += data; }); socket.on("close", () => { callback(null, response.length > 0 ? response : null, null); }); socket.on("error", (error) => { console.error(`Encoder error: ${error}`); }); } function convertTileRequest(req, res) { if (config.verbose) { console.log(req.originalUrl); } if (!req.query.url) { if (config.verbose) { console.error("Missing `url` parameter"); } res.status(400).send("Missing `url` parameter"); return; } let url; try { url = new URL(req.query.url); } catch { if (config.verbose) { console.error(`Invalid url: ${req.query.url}`); } res.status(400).send(`Invalid url: ${req.query.url}`); return; } fetch(url) .then(async (tileResponse) => { if (!tileResponse.ok) { res .status(tileResponse.status) .send( `Tile request error: ${req.query.url} - ${tileResponse.status} ${tileResponse.statusText}`, ); return; } if (!existsSync(config.cachePath)) { mkdirSync(config.cachePath, { recursive: true }); } const file = createWriteStream(join(config.cachePath, randomUUID())); try { await pipeline(Readable.fromWeb(tileResponse.body), file); } catch (error) { if (config.verbose) { console.error(`Tile download failed: ${req.query.url} - ${error}`); } res .status(500) .send(`Tile download failed: ${req.query.url} - ${error}`); return; } convertTileResponse(file.path, res); }) .catch((error) => { if (config.verbose) { console.error(`Request failed: ${req.query.url} - ${error}`); } res.status(500).send(`Request failed: ${req.query.url} - ${error}`); }); } function runCLISetup() { console.log(`Building CLI tools at ${config.cliToolsPath}`); execSync("./gradlew cli", { cwd: config.cliToolsPath }); if (config.noencodingserver) { return; } const server = spawn("java", [ "-jar", `${config.encoderPath}`, "--server", `${config.encoderPort}`, ]); if (config.verbose) { server.stdout.on("data", (data) => { console.log(`Encoder: ${data}`); }); server.stderr.on("data", (data) => { console.error(`Encoder: ${data}`); }); } server.on("close", (code) => { console.log(`Encoder closed: ${code}`); }); } export { convertSourceRequest, convertStyleRequest, convertTileRequest, runCLISetup, }; ================================================ FILE: java/encoding-server/eslint.config.mjs ================================================ import eslint from "@eslint/js"; import nPlugin from "eslint-plugin-n"; import globals from "globals"; export default [ eslint.configs.recommended, { plugins: { n: nPlugin, }, languageOptions: { globals: { ...globals.node, }, }, rules: { "n/prefer-node-protocol": "error", }, }, { ignores: ["node_modules/**", "cache/**"], }, ]; ================================================ FILE: java/encoding-server/package.json ================================================ { "name": "encoding-server", "version": "1.0.0", "main": "server.mjs", "scripts": { "start": "node server.mjs", "lint": "eslint ." }, "author": "", "license": "ISC", "description": "", "dependencies": { "cors": "^2.8.5", "express": "^5.2.0", "https": "^1.0.0" }, "devDependencies": { "@eslint/js": "^9.38.0", "eslint": "^9.38.0", "eslint-plugin-n": "^17.23.1", "globals": "^16.4.0" } } ================================================ FILE: java/encoding-server/server.mjs ================================================ import cors from "cors"; import express from "express"; import config from "./config.mjs"; import { convertSourceRequest, convertStyleRequest, convertTileRequest, runCLISetup, } from "./convert.mjs"; const app = express(); app.use(cors()); app.use("/style", convertStyleRequest); app.use("/source", convertSourceRequest); app.use("/tile", convertTileRequest); app.listen(config.port, config.host, () => { runCLISetup(); console.log(`Server started on port ${config.port}`); }); ================================================ FILE: java/gradle/libs.versions.toml ================================================ [versions] tileverse = "1.3.3" jmh = "1.37" junit-jupiter = "6.0.3" kotlin-jvm = "2.3.10" lombok = "1.18.38" [libraries] commons-cli = { group = "commons-cli", name = "commons-cli", version = "1.11.0"} commons-compress = { group = "org.apache.commons", name = "commons-compress", version = "1.28.0" } commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version = "3.20.0" } earcut4j = { group = "org.maplibre", name = "earcut4j", version = "3.0.0" } gson = { group = "com.google.code.gson", name = "gson", version = "2.13.2" } guava = { group = "com.google.guava", name = "guava", version = "33.5.0-jre" } hppc = { group = "com.carrotsearch", name = "hppc", version = "0.10.0" } jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version = "2.21.1" } javaFastPFOR = { group = "me.lemire.integercompression", name = "JavaFastPFOR", version = "0.3.10" } jakarta-annotation-api = { group = "jakarta.annotation", name = "jakarta.annotation-api", version = "3.0.0" } java-vector-tile = { group = "no.ecc.vectortile", name = "java-vector-tile", version = "1.3.23" } jetbrains-annotations = { group = "org.jetbrains", name = "annotations", version = "26.1.0" } jmh-core = { group = "org.openjdk.jmh", name = "jmh-core", version.ref = "jmh" } jmh-generator-annprocess = { group = "org.openjdk.jmh", name = "jmh-generator-annprocess", version.ref = "jmh"} jts-core = { group = "org.locationtech.jts", name = "jts-core", version = "1.20.0" } jts-io-common = { group = "org.locationtech.jts.io", name = "jts-io-common", version = "1.20.0" } junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit-jupiter" } junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junit-jupiter" } junit-jupiter-params = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "junit-jupiter" } junit-jupiter-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher", version.ref = "junit-jupiter"} kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin-jvm" } mapbox-vector-tile-java = { group = "io.github.sebasbaumh", name = "mapbox-vector-tile-java", version = "25.1.0" } mbtiles4j = { group = "org.imintel", name = "mbtiles4j", version = "1.0.6" } planetiler-core = { module= "com.onthegomap.planetiler:planetiler-core", version="0.10.0" } protobuf-java = { group = "com.google.protobuf", name = "protobuf-java", version = "4.34.0" } log4j-api = { group = "org.apache.logging.log4j", name = "log4j-api", version = "2.25.3" } log4j-slf4j-impl = { group = "org.apache.logging.log4j", name = "log4j-slf4j-impl", version = "2.25.3" } sqlite-jdbc = { group = "org.xerial", name = "sqlite-jdbc", version = "3.51.2.0" } tileverse-io = { group = "io.tileverse", name = "tileverse-bom", version.ref = "tileverse" } tileverse-rangereader-all = { module= "io.tileverse.rangereader:tileverse-rangereader-all" } lombok = { group = "org.projectlombok", name = "lombok", version.ref = "lombok" } [plugins] diffplug-spotless = { id = "com.diffplug.spotless", version = "8.2.1"} gradleup-shadow = { id = "com.gradleup.shadow", version = "9.3.2"} me-champeau-jmh = { id = "me.champeau.jmh", version = "0.7.3" } maven-publish = { id = "com.vanniktech.maven.publish", version = "0.36.0" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-jvm" } ================================================ FILE: java/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: java/gradlew ================================================ #!/bin/sh # # Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # SPDX-License-Identifier: Apache-2.0 # ############################################################################## # # Gradle start up script for POSIX generated by Gradle. # # Important for running: # # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is # noncompliant, but you have some other compliant shell such as ksh or # bash, then to run this script, type that shell name before the whole # command line, like: # # ksh Gradle # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # «${var#prefix}», «${var%suffix}», and «$( cmd )»; # * compound commands having a testable exit status, especially «case»; # * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # # (2) This script targets any POSIX shell, so it avoids extensions provided # by Bash, Ksh, etc; in particular arrays are avoided. # # The "traditional" practice of packing multiple parameters into a # space-separated string is a well documented source of bugs and security # problems, so this is (mostly) avoided, by progressively accumulating # options in "$@", and eventually passing that to Java. # # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; # see the in-line comments for details. # # There are tweaks for specific operating systems such as AIX, CygWin, # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. # ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link app_path=$0 # Need this for daisy-chained symlinks. while APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path [ -h "$app_path" ] do ls=$( ls -ld "$app_path" ) link=${ls#*' -> '} case $link in #( /*) app_path=$link ;; #( *) app_path=$APP_HOME$link ;; esac done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum warn () { echo "$*" } >&2 die () { echo echo "$*" echo exit 1 } >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "$( uname )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( NONSTOP* ) nonstop=true ;; esac # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD=$JAVA_HOME/jre/sh/java else JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD=java if ! command -v java >/dev/null 2>&1 then die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line # * the main class name # * -classpath # * -D...appname settings # * --module-path (only if needed) # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) # Now convert the arguments - kludge to limit ourselves to /bin/sh for arg do if case $arg in #( -*) false ;; # don't mess with options #( /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath [ -e "$t" ] ;; #( *) false ;; esac then arg=$( cygpath --path --ignore --mixed "$arg" ) fi # Roll the args list around exactly as many times as the number of # args, so each arg winds up back in the position where it started, but # possibly modified. # # NB: a `for` loop captures its iteration list before it begins, so # changing the positional parameters here affects neither the number of # iterations, nor the values presented in `arg`. shift # remove old arg set -- "$@" "$arg" # push replacement arg done fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. if ! command -v xargs >/dev/null 2>&1 then die "xargs is not available" fi # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. # # In Bash we could simply go: # # readarray ARGS < <( xargs -n1 <<<"$var" ) && # set -- "${ARGS[@]}" "$@" # # but POSIX shell has neither arrays nor command substitution, so instead we # post-process each arg (as a line of input to sed) to backslash-escape any # character that might be a shell metacharacter, then use eval to reverse # that process (while maintaining the separation between arguments), and wrap # the whole thing up as a single "set" statement. # # This will of course break if any of these variables contains a newline or # an unmatched quote. # eval "set -- $( printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | xargs -n1 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | tr '\n' ' ' )" '"$@"' exec "$JAVACMD" "$@" ================================================ FILE: java/gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @rem SPDX-License-Identifier: Apache-2.0 @rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute echo. 1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. 1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! set EXIT_CODE=%ERRORLEVEL% if %EXIT_CODE% equ 0 set EXIT_CODE=1 if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: java/lombok.config ================================================ # Add generated annotations to generated code lombok.addLombokGeneratedAnnotation = true # Add nullability annotations to generated code lombok.addNullAnnotations = jspecify # Suppress warnings on generated code lombok.addSuppressWarnings = true ================================================ FILE: java/mlt-cli/build.gradle ================================================ plugins { id 'java-library' id 'jacoco' alias(libs.plugins.kotlin.jvm) alias(libs.plugins.diffplug.spotless) alias(libs.plugins.gradleup.shadow) } repositories { mavenCentral() maven { url = 'https://maven.ecc.no/releases' } maven { url = 'https://repo.osgeo.org/repository/release/' content { includeGroup 'org.geotools' includeGroup 'org.geotools.ogc' includeGroup 'org.eclipse.imagen' includeGroup 'it.geosolutions.jgridshift' } } } dependencies { // Core library dependency implementation project(':mlt-core') // CLI-specific dependencies implementation(libs.commons.cli) implementation(libs.commons.compress) implementation(libs.mbtiles4j) implementation(libs.sqlite.jdbc) // Additional dependencies needed by CLI classes implementation(libs.gson) implementation(libs.protobuf.java) implementation(libs.jakarta.annotation.api) implementation(libs.jetbrains.annotations) implementation(libs.jts.core) implementation(libs.jts.io.common) implementation(libs.log4j.api) implementation(libs.log4j.slf4j.impl) runtimeOnly(libs.jackson.databind) // Required for JSON configuration of log4j2 // Kotlin implementation(libs.kotlin.stdlib) // pmtiles support implementation(libs.planetiler.core) implementation(platform(libs.tileverse.io)) implementation(libs.tileverse.rangereader.all) testImplementation(libs.junit.jupiter.api) testRuntimeOnly(libs.junit.jupiter.engine) testRuntimeOnly(libs.junit.jupiter.platform.launcher) testImplementation(libs.junit.jupiter.params) testImplementation 'org.mockito:mockito-core:5.2.0' testImplementation 'org.mockito:mockito-inline:5.2.0' testImplementation 'org.mockito.kotlin:mockito-kotlin:5.2.0' } // Exclude slf4j-simple from all configurations to prevent conflicts with log4j-slf4j-impl configurations.all { exclude group: 'org.slf4j', module: 'slf4j-simple' // no control over log output exclude group: 'org.slf4j', module: 'log4j-over-slf4j' // only for log4j 1.x } test { useJUnitPlatform() testLogging { outputs.upToDateWhen { false } showStandardStreams = true events "passed", "skipped", "failed" exceptionFormat = "full" } finalizedBy jacocoTestReport } jacocoTestReport { reports { xml.required = true html.required = false } dependsOn test } java { toolchain { languageVersion = JavaLanguageVersion.of(21) } } kotlin { jvmToolchain { languageVersion.set(JavaLanguageVersion.of(21)) } } spotless { groovy { target 'build.gradle' leadingTabsToSpaces(4) trimTrailingWhitespace() endWithNewline() } kotlin { target 'src/**/*.kt' ktlint() } java { importOrder() target 'src/*/java/**/*.java' googleJavaFormat() removeUnusedImports() } } gradle.projectsEvaluated { tasks.withType(JavaCompile).tap { configureEach { options.compilerArgs << "-Xlint:unchecked" options.compilerArgs << "-Xlint:deprecation" options.compilerArgs << "-Xlint:all" } } } tasks.withType(Test).configureEach { useJUnitPlatform() } // Create CLI JARs ["encode", "decode"].each { name -> task "$name"(type: com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar, dependsOn: [compileJava, ':mlt-core:jar']) { archiveFileName = name + ".jar" archiveClassifier = "cli" duplicatesStrategy = DuplicatesStrategy.EXCLUDE configurations = project.configurations.named('runtimeClasspath').map { [it] } // Exclude the log config file from some dependency and include our own. // This only works because they use different extensions, else the exclude would remove both. exclude 'log4j2.properties' from('src/main/resources') { include 'log4j2.json' duplicatesStrategy = DuplicatesStrategy.WARN } // Filter out dependency JAR signatures to prevent // `SecurityException: Invalid signature file digest for Manifest main attributes` exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA' // Tileverse relies on the contents of META-INF/services to register service providers. // By default, these are ignored when building a fat JAR, so we need to merge them. filesMatching('META-INF/services/**') { duplicatesStrategy = DuplicatesStrategy.INCLUDE } // Log4j relies on the contents of META-INF/org/apache/logging/log4j/core/config/plugins to register plugins. filesMatching('META-INF/org/apache/logging/log4j/core/config/plugins/**') { duplicatesStrategy = DuplicatesStrategy.INCLUDE } mergeServiceFiles() manifest.from jar.manifest with jar } } encode.manifest.attributes 'Implementation-Title': 'encode an mlt from an mvt', 'Main-Class': 'org.maplibre.mlt.cli.Encode' decode.manifest.attributes 'Implementation-Title': 'decode an mlt', 'Main-Class': 'org.maplibre.mlt.cli.Decode' tasks.register('cli') { // Uncomment the following line to clear out the jars before rebuilding delete "${layout.buildDirectory}/libs" dependsOn encode dependsOn decode } ================================================ FILE: java/mlt-cli/src/main/kotlin/org/maplibre/mlt/cli/Conversion.kt ================================================ package org.maplibre.mlt.cli import com.onthegomap.planetiler.geo.TileCoord import org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream import org.apache.commons.compress.compressors.deflate.DeflateCompressorOutputStream import org.apache.commons.compress.compressors.deflate.DeflateParameters import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream import org.apache.commons.compress.compressors.gzip.GzipParameters import org.apache.commons.lang3.mutable.MutableBoolean import org.maplibre.mlt.compare.CompareHelper import org.maplibre.mlt.compare.CompareHelper.CompareMode import org.maplibre.mlt.converter.ColumnMapping import org.maplibre.mlt.converter.ConversionConfig import org.maplibre.mlt.converter.FeatureTableOptimizations import org.maplibre.mlt.converter.MltConverter import org.maplibre.mlt.converter.mvt.MvtUtils import org.maplibre.mlt.decoder.MltDecoder import org.maplibre.mlt.metadata.tileset.MltMetadata import java.io.BufferedInputStream import java.io.ByteArrayOutputStream import java.io.IOException import java.io.InputStream import java.io.OutputStream import java.sql.Connection import java.sql.SQLException import java.util.Optional import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicLong import java.util.stream.Collectors import java.util.zip.Inflater import java.util.zip.InflaterInputStream fun decompress(srcStream: InputStream): ByteArray { var decompressInputStream: InputStream? = null // Check for common compression formats by looking at the header bytes // Buffered stream is not closed here because it would also close the underlying stream val readStream = BufferedInputStream(srcStream) if (readStream.available() > 3) { readStream.mark(4) val header = readStream.readNBytes(4) readStream.reset() if (DeflateCompressorInputStream.matches(header, header.size)) { // deflate with zlib header val inflater = Inflater(false) decompressInputStream = InflaterInputStream(readStream, inflater) } else if (header[0].toInt() == 0x1f && header[1] == 0x8b.toByte()) { // TODO: why doesn't GZIPInputStream work here? // decompressInputStream = new GZIPInputStream(readStream); decompressInputStream = GzipCompressorInputStream(readStream) } } if (decompressInputStream != null) { ByteArrayOutputStream().use { outputStream -> decompressInputStream.transferTo(outputStream) return outputStream.toByteArray() } } return readStream.readAllBytes() } fun createCompressStream( src: OutputStream, compressionType: String?, ): OutputStream { if (compressionType == "gzip") { val parameters = GzipParameters() parameters.setCompressionLevel(9) return GzipCompressorOutputStream(src, parameters) } if (compressionType == "deflate") { val parameters = DeflateParameters() parameters.setCompressionLevel(9) parameters.setWithZlibHeader(false) return DeflateCompressorOutputStream(src, parameters) } return src } fun vacuumDatabase(connection: Connection): Boolean { logger.debug("Optimizing database") try { connection.createStatement().use { stmt -> stmt.execute("VACUUM") return true } } catch (ex: SQLException) { logger.error("Failed to optimize database", ex) } return false } fun getTileLabel( x: Long, y: Long, z: Int, ): String = String.format("%d:%d,%d", z, x, y) fun getTileLabel(c: TileCoord): String = getTileLabel(c.x.toLong(), c.y.toLong(), c.z) fun getTileLabels( coords: Iterable, limit: Int, ): String = coords.joinToString( separator = ", ", limit = limit, truncated = "...", transform = ::getTileLabel, ) fun logColumnMappings( x: Long, y: Long, z: Int, metadata: MltMetadata.TileSetMetadata, ) { if (!logger.isTraceEnabled) { return } for (table in metadata.featureTables) { for (column in table.columns) { val complex = column.field().type().complexType() if (complex != null && complex.physicalType == MltMetadata.ComplexType.STRUCT ) { val mappings = complex.children .map { child -> (column.getName() ?: "") + child.name } .toSortedSet() .joinToString(", ") val added = MutableBoolean(false) val entry = loggedColumnMappings.computeIfAbsent(mappings, { _ -> added.setTrue() loggedColumnMappingId.incrementAndGet() }) if (added.isTrue) { logger.trace(colMapMarker, "{}:{},{} Found new column mapping {} for {}: {}", z, x, y, entry, table.name, mappings) } else { logger.trace(colMapMarker, "{}:{},{} Found existing column mapping for {}: {}", z, x, y, table.name, entry) } } } } } /** * Converts a tile from MVT to MLT, handling de- and re-compression * * @param x The x-coordinate of the tile. * @param y The y-coordinate of the tile. * @param z The zoom level of the tile. * @param srcTileData The source tile data as a byte array. * @param config The configuration for the conversion process * @param compressionRatioThreshold An optional threshold for the compression ratio to determine * whether compression is worth the need to decompress it. The compressed version will only be * used if its size is less than the original size multiplied by this ratio. If not present, a * compressed result will be used even if it's larger than the original. * @param compressionFixedThreshold An optional fixed byte threshold to determine whether * compression is worth the need to decompress it. The compressed version will only be used if * its size is at least this many bytes smaller than the original size. If not present, a * compressed result will be used even if it's larger than the original. * @param didCompress A mutable boolean to indicate whether compression was applied * @return The converted tile data as a byte array, or null if the conversion failed. */ fun convertTile( x: Long, y: Long, z: Int, srcTileData: ByteArray, config: EncodeConfig, compressionRatioThreshold: Optional, compressionFixedThreshold: Optional, didCompress: MutableBoolean?, ): ByteArray? { try { // Decode the source tile data into an intermediate representation. val decodedMvTile = MvtUtils.decodeMvt(srcTileData) val isIdPresent = true val metadata = MltConverter.createTilesetMetadata( decodedMvTile, config.conversionConfig, config.columnMappingConfig, isIdPresent, ) // Print column mappings if verbosity level is high. logColumnMappings(x, y, z, metadata) // Apply column mappings and update the conversion configuration. val targetConfig = applyColumnMappingsToConversionConfig(config, metadata) // Convert the tile using the updated configuration and tessellation source. var tileData = MltConverter.encode( decodedMvTile, metadata, targetConfig, config.tessellateSource, ) // Apply compression if specified. val uncompressedTileData = tileData if (config.compressionType != null) { ByteArrayOutputStream().use { outputStream -> createCompressStream( outputStream, config.compressionType, ).use { compressStream -> compressStream.write(tileData) } // Evaluate whether the compressed version is worth using. if (( compressionFixedThreshold.isEmpty || outputStream.size() <= tileData.size - compressionFixedThreshold.get() ) && ( compressionRatioThreshold.isEmpty || outputStream.size() <= tileData.size * compressionRatioThreshold.get() ) ) { if (logger.isTraceEnabled) { val compressedSize = outputStream.size() val originalSize = tileData.size logger.trace( compressMarker, "{} compressed {} to {} bytes ({}%)", getTileLabel(x, y, z), originalSize, compressedSize, String.format( "%.1f", 100.0 * compressedSize / originalSize, ), ) } totalCompressedInput.addAndGet(tileData.size.toLong()) totalCompressedOutput.addAndGet(outputStream.size().toLong()) totalCompressedTiles.incrementAndGet() if (didCompress != null) { didCompress.setTrue() } tileData = outputStream.toByteArray() } else { if (logger.isTraceEnabled) { val compressedSize = outputStream.size() val originalSize = tileData.size val pctStr = String.format( "%.1f", 100.0 * compressedSize / originalSize, ) logger.trace( compressMarker, "Compression of {} not effective ({} vs {} bytes, {}%), using uncompressed", getTileLabel(x, y, z), originalSize, compressedSize, pctStr, ) } totalUncompressedTiles.incrementAndGet() if (didCompress != null) { didCompress.setFalse() } } } } if (config.compareMode != CompareMode.None) { logger.trace("Decoding converted tile {}:{},{} for comparison", z, x, y) val decodedTile = MltDecoder.decodeMlTile(uncompressedTileData) val difference = CompareHelper.compareTiles( decodedTile, decodedMvTile, config.compareMode, targetConfig.layerFilterPattern(), targetConfig.layerFilterInvert(), ) if (difference.isPresent) { logger.warn("Decoded tile {}:{},{} doesn't match: {}", z, x, y, difference) } else { logger.trace("Tiles match: {}:{},{}", z, x, y) } } return tileData } catch (ex: IOException) { // Log an error message if tile conversion fails. logger.error("Failed to process tile {}:{},{}", z, x, y, ex) } return null } /** In batch conversion, we don't have the column names yet when we set up the config, so we need to apply the mappings to an existing immutable config object using the column names from a decoded tile metadata. */ fun applyColumnMappingsToConversionConfig( config: EncodeConfig, metadata: MltMetadata.TileSetMetadata, ): ConversionConfig { val conversionConfig = config.conversionConfig // If the config already has optimizations, don't modify it if (!conversionConfig.optimizations().isEmpty()) { return conversionConfig } // Warn if both patterns match on any tables val warnTables = metadata.featureTables .stream() .filter { table: MltMetadata.FeatureTable? -> config.sortFeaturesPattern != null && config.sortFeaturesPattern.matcher(table!!.name).matches() }.filter { table: MltMetadata.FeatureTable? -> config.regenIDsPattern != null && config.regenIDsPattern.matcher(table!!.name).matches() }.map { table: MltMetadata.FeatureTable? -> table!!.name } .collect(Collectors.joining(",")) if (!warnTables.isEmpty()) { logger.warn( "The --{} and --{} options are incompatible: {}", EncodeCommandLine.SORT_FEATURES_OPTION, EncodeCommandLine.REGEN_IDS_OPTION, warnTables, ) } // Now that we have the actual column names from the metadata, we can determine which column // mappings apply to which tables and create the optimizations for each table accordingly. val optimizationMap = metadata.featureTables .stream() .collect( Collectors.toUnmodifiableMap( { table: MltMetadata.FeatureTable -> table.name }, { table: MltMetadata.FeatureTable -> FeatureTableOptimizations( config.sortFeaturesPattern != null && config.sortFeaturesPattern .matcher(table.name) .matches(), config.regenIDsPattern != null && config.regenIDsPattern.matcher(table.name).matches(), config.columnMappingConfig.entries .stream() .filter { entry -> entry .key .matcher( table.name, ).matches() }.flatMap { entry -> entry.value.stream() }.toList(), ) }, ), ) // re-create the config with the applied column mappings return conversionConfig.toBuilder().optimizations(optimizationMap).build() } val totalCompressedInput = AtomicLong(0L) val totalCompressedOutput = AtomicLong(0L) val totalCompressedTiles = AtomicLong(0L) val totalUncompressedTiles = AtomicLong(0L) val loggedColumnMappingId = AtomicLong(0L) val loggedColumnMappings = ConcurrentHashMap() const val MBTILES_METADATA_MIME_TYPE = "application/vnd.maplibre-tile" ================================================ FILE: java/mlt-cli/src/main/kotlin/org/maplibre/mlt/cli/Decode.kt ================================================ package org.maplibre.mlt.cli import org.apache.commons.cli.CommandLine import org.apache.commons.cli.DefaultParser import org.apache.commons.cli.Option import org.apache.commons.cli.Options import org.apache.commons.cli.ParseException import org.apache.commons.cli.help.HelpFormatter import org.maplibre.mlt.data.MapLibreTile import org.maplibre.mlt.decoder.MltDecoder import org.slf4j.Logger import org.slf4j.LoggerFactory import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Paths object Decode { private const val FILE_NAME_ARG = "mlt" private const val PRINT_MLT_OPTION = "printmlt" private const val TIMER_OPTION = "timer" @JvmStatic fun main(args: Array) { try { run(args) } catch (ex: Exception) { logger.error("Decoding failed", ex) System.exit(1) } } private fun run(args: Array) { val options = Options() options.addOption( Option .builder() .longOpt(FILE_NAME_ARG) .hasArg(true) .desc("Path to the input MLT file to read ([REQUIRED])") .required(true) .get(), ) options.addOption( Option .builder() .longOpt(PRINT_MLT_OPTION) .hasArg(false) .desc("Print the MLT tile after encoding it ([OPTIONAL], default: false)") .required(false) .get(), ) options.addOption( Option .builder() .longOpt(TIMER_OPTION) .hasArg(true) .optionalArg(true) .argName("count") .desc("Print the time it takes, in ms, to decode a tile times ([OPTIONAL], default 1)") .required(false) .get(), ) if (args.isEmpty()) { showHelp(options) System.exit(1) } try { val cmd = DefaultParser().parse(options, args) run(cmd) } catch (ex: ParseException) { System.err.println(ex.message) showHelp(options) throw ex } catch (ex: Exception) { System.err.println("Failed: " + ex.message) ex.printStackTrace(System.err) throw ex } } private fun run(cmd: CommandLine) { val fileName = cmd.getOptionValue(FILE_NAME_ARG) if (fileName == null || fileName.isEmpty()) { throw ParseException("Missing required argument: " + FILE_NAME_ARG) } val willPrintMLT = cmd.hasOption(PRINT_MLT_OPTION) val willTime = cmd.hasOption(TIMER_OPTION) val decodeIterations = cmd.getOptionValue(TIMER_OPTION, "1").toInt().coerceAtLeast(1) val inputTilePath = Paths.get(fileName) require(Files.exists(inputTilePath)) { "Input mlt tile path does not exist: " + inputTilePath } val mltTileBuffer = Files.readAllBytes(inputTilePath) val timer = Timer() var decodedTile: MapLibreTile? = null for (i in 0 until decodeIterations) { decodedTile = MltDecoder.decodeMlTile(mltTileBuffer) } if (willTime) timer.stop("decoding") if (willPrintMLT && decodedTile != null) { System.out.write(decodedTile.toJson().toByteArray(StandardCharsets.UTF_8)) } } private fun showHelp(options: Options) { HelpFormatter .builder() .setShowSince(false) .get() .printHelp(Decode::class.java.name, "", options, null, true) } private val logger: Logger = LoggerFactory.getLogger(Decode::class.java) } ================================================ FILE: java/mlt-cli/src/main/kotlin/org/maplibre/mlt/cli/Encode.kt ================================================ package org.maplibre.mlt.cli import org.apache.commons.cli.CommandLine import org.apache.commons.io.FilenameUtils import org.apache.logging.log4j.Level import org.apache.logging.log4j.core.config.Configurator import org.maplibre.mlt.cli.EncodeCommandLine.getColumnMappings import org.maplibre.mlt.compare.CompareHelper import org.maplibre.mlt.compare.CompareHelper.CompareMode import org.maplibre.mlt.converter.ConversionConfig import org.maplibre.mlt.converter.MltConverter import org.maplibre.mlt.converter.encodings.fsst.FsstEncoder import org.maplibre.mlt.converter.encodings.fsst.FsstJni import org.maplibre.mlt.converter.mvt.MvtUtils import org.maplibre.mlt.decoder.MltDecoder import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.ByteArrayInputStream import java.io.File import java.io.IOException import java.net.URI import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.InvalidPathException import java.nio.file.Path import java.nio.file.Paths object Encode { @JvmStatic fun main(args: Array) { if (!run(args)) { System.exit(1) } } fun run(args: Array): Boolean { try { val cmd = EncodeCommandLine.getCommandLine(args) if (cmd == null) { return false } if (cmd.hasOption(EncodeCommandLine.SERVER_ARG)) { val port = cmd.getOptionValue(EncodeCommandLine.SERVER_ARG, "3001").toInt() // never returns Server().run(port) } return run(cmd) } catch (ex: Exception) { logger.error("Failed", ex) return false } } private fun run(cmd: CommandLine): Boolean { val tileFileNames = cmd.getOptionValues(EncodeCommandLine.INPUT_TILE_ARG) val sortFeaturesPattern = cmd.sortFeaturesPattern val regenIDsPattern = cmd.regenIDsPattern val outlineFeatureTables = cmd.outlineFeatureTables val useFSSTJava = cmd.useFSSTJava val useFSSTNative = cmd.useFSSTNative val tessellateURI = cmd.tessellateSource?.let { URI(it) } val tessellatePolygons = cmd.tessellatePolygons val compressionType = cmd.compressionType val filterPattern = cmd.filterPattern val filterInvert = cmd.filterInvert val minZoom = cmd.minZoom val maxZoom = cmd.maxZoom val logLevel = cmd.logLevel val threadCount = cmd.threadCount Configurator.setRootLevel(logLevel) // PMTiles logs stats at INFO. Enable that only if the user has selected at least debug. // Note: `isLessSpecificThan` is actually less-than-or-equal. Configurator.setLevel( "com.onthegomap.planetiler.pmtiles.WriteablePmtiles", if (logLevel.isLessSpecificThan(Level.DEBUG)) Level.INFO else Level.OFF, ) val columnMappings = getColumnMappings(cmd) val taskRunner = createBoundedNonRejectingTaskRunner(threadCount) logger.debug("Using {} thread(s)", taskRunner.threadCount + 1) logger.debug( "Using column mappings: {}", if (columnMappings.isEmpty()) "none" else columnMappings.toString(), ) var useFSST = false if (useFSSTNative) { if (FsstJni.isLoaded() && FsstEncoder.useNative(true)) { useFSST = true } else { logger.warn("Native FSST could not be loaded", FsstJni.getLoadError()) } } else if (useFSSTJava) { logger.debug("Using Java FSST encoder") FsstEncoder.useNative(false) useFSST = true } val typeMismatchPolicy = if (cmd.enableCoerceOnTypeMismatch) { ConversionConfig.TypeMismatchPolicy.COERCE } else if (cmd.enableElideOnTypeMismatch) { ConversionConfig.TypeMismatchPolicy.ELIDE } else { ConversionConfig.TypeMismatchPolicy.FAIL } val conversionConfig = ConversionConfig .builder() .includeIds(!cmd.hasOption(EncodeCommandLine.EXCLUDE_IDS_OPTION)) .useFastPFOR(cmd.hasOption(EncodeCommandLine.FASTPFOR_ENCODING_OPTION)) .useFSST(useFSST) .typeMismatchPolicy(typeMismatchPolicy) .preTessellatePolygons(tessellatePolygons) .useMortonEncoding(!cmd.hasOption(EncodeCommandLine.NO_MORTON_OPTION)) .outlineFeatureTableNames( if (outlineFeatureTables != null) outlineFeatureTables.toList() else listOf(), ).layerFilterPattern(filterPattern) .layerFilterInvert(filterInvert) .integerEncodingOption(ConversionConfig.IntegerEncodingOption.AUTO) .build() if (outlineFeatureTables != null && outlineFeatureTables.size > 0) { logger.debug( "Including outlines for layers: {}", outlineFeatureTables.joinToString(", "), ) } val encodeConfig = EncodeConfig( columnMappingConfig = columnMappings, conversionConfig = conversionConfig, tessellateSource = tessellateURI, sortFeaturesPattern = sortFeaturesPattern, regenIDsPattern = regenIDsPattern, compressionType = compressionType, minZoom = minZoom, maxZoom = maxZoom, willOutput = cmd.hasOption(EncodeCommandLine.OUTPUT_FILE_ARG) || cmd.hasOption(EncodeCommandLine.OUTPUT_DIR_ARG), willDecode = cmd.hasOption(EncodeCommandLine.DECODE_OPTION), willPrintMLT = cmd.hasOption(EncodeCommandLine.PRINT_MLT_OPTION), willPrintMVT = cmd.hasOption(EncodeCommandLine.PRINT_MVT_OPTION), compareProp = cmd.hasOption(EncodeCommandLine.COMPARE_PROP_OPTION) || cmd.hasOption(EncodeCommandLine.COMPARE_ALL_OPTION), compareGeom = cmd.hasOption(EncodeCommandLine.COMPARE_GEOM_OPTION) || cmd.hasOption(EncodeCommandLine.COMPARE_ALL_OPTION), willTime = cmd.hasOption(EncodeCommandLine.TIMER_OPTION), taskRunner = taskRunner, continueOnError = cmd.hasOption(EncodeCommandLine.CONTINUE_OPTION), logCacheStats = cmd.hasOption(EncodeCommandLine.CACHE_STATS_OPTION), ) if (tileFileNames != null && tileFileNames.size > 0) { require(!(tileFileNames.size > 1 && cmd.hasOption(EncodeCommandLine.OUTPUT_FILE_ARG))) { ( "Multiple input files not allowed with single output file, use --" + EncodeCommandLine.OUTPUT_DIR_ARG ) } for (tileFileName in tileFileNames) { val outputPath = getOutputPath(cmd, tileFileName, "mlt") if (outputPath == null) { continue } logger.debug("Converting {} to {}", tileFileName, outputPath) encodeTile(0, 0, 0, tileFileName, outputPath, encodeConfig) } } else if (cmd.hasOption(EncodeCommandLine.INPUT_MBTILES_ARG)) { // Converting all the tiles in an MBTiles file val inputPath = cmd.getOptionValue(EncodeCommandLine.INPUT_MBTILES_ARG) val outputPath = getOutputPath(cmd, inputPath, "mlt.mbtiles") if (!encodeMBTiles(inputPath, outputPath, encodeConfig)) { return false } } else if (cmd.hasOption(EncodeCommandLine.INPUT_OFFLINEDB_ARG)) { val inputPath = cmd.getOptionValue(EncodeCommandLine.INPUT_OFFLINEDB_ARG) var ext = FilenameUtils.getExtension(inputPath) if (ext != null && !ext.isEmpty()) { ext = "." + ext } val outputPath = getOutputPath(cmd, inputPath, "mlt" + ext) if (!encodeOfflineDB(Path.of(inputPath), outputPath, encodeConfig)) { return false } } else if (cmd.hasOption(EncodeCommandLine.INPUT_PMTILES_ARG)) { val inputPath = cmd.getOptionValue(EncodeCommandLine.INPUT_PMTILES_ARG) var ext = FilenameUtils.getExtension(inputPath) if (ext != null && !ext.isEmpty()) { ext = "." + ext } var outputPath = getOutputPath(cmd, inputPath, "mlt" + ext) if (outputPath == null) { return false } val inputURI = getInputURI(inputPath) outputPath = outputPath.toAbsolutePath() if (!encodePMTiles(inputURI, outputPath, encodeConfig)) { return false } } if (logger.isDebugEnabled) { val input = totalCompressedInput.get() val output = totalCompressedOutput.get() val compressed = totalCompressedTiles.get() val uncompressed = totalUncompressedTiles.get() val total = compressed + uncompressed val sizePercentStr = if (input > 0) String.format(" (%.1f%%)", 100.0 * output / input) else "" val countPercentStr = if (total > 0) String.format(" (%.1f%%)", 100.0 * compressed / total) else "" logger.debug( "Compressed {} bytes to {} bytes{} in {} of {}{} tiles", input, output, sizePercentStr, compressed, total, countPercentStr, ) } return true } /** Convert a single tile from an individual file */ private fun encodeTile( x: Long, y: Long, z: Int, tileFileName: String, outputPath: Path, config: EncodeConfig, ) { val willCompare = config.compareMode != CompareMode.None val inputTilePath = Paths.get(tileFileName) val tileData = Files.readAllBytes(inputTilePath).let { try { decompress(ByteArrayInputStream(it)) } catch (ex: IOException) { logger.error("Failed to decompress tile {}", tileFileName, ex) return } } val decodedMvTile = MvtUtils.decodeMvt(tileData) val willTime = config.willTime val timer = if (willTime) Timer() else null val isIdPresent = true val metadata = MltConverter.createTilesetMetadata( decodedMvTile, config.conversionConfig, config.columnMappingConfig, isIdPresent, ) logColumnMappings(x, y, z, metadata) val targetConfig = applyColumnMappingsToConversionConfig(config, metadata) val mlTile = MltConverter.encode( decodedMvTile, metadata, targetConfig, config.tessellateSource, ) timer?.stop("encoding") if (config.willOutput) { logger.debug("Writing converted tile to {}", outputPath) try { Files.write(outputPath, mlTile) } catch (ex: IOException) { logger.error("Failed to write tile to {}", outputPath, ex) } } if (config.willPrintMVT) { System.out.write(decodedMvTile.toJson().toByteArray(StandardCharsets.UTF_8)) } val needsDecoding = config.willDecode || willCompare || config.willPrintMLT if (needsDecoding) { logger.debug("Decoding converted tile...") timer?.restart() val decodedTile = MltDecoder.decodeMlTile(mlTile) timer?.stop("decoding") if (config.willPrintMLT) { System.out.write(decodedTile.toJson().toByteArray(StandardCharsets.UTF_8)) } if (willCompare) { val difference = CompareHelper.compareTiles( decodedTile, decodedMvTile, config.compareMode, targetConfig.layerFilterPattern(), targetConfig.layerFilterInvert(), ) if (difference.isPresent) { logger.warn("Tiles do not match: {}", difference) } else { logger.debug("Tiles match: {}:{},{}", z, x, y) } } } } private fun getInputURI(inputArg: String): URI { val file = File(inputArg) return if (file.isFile) { file.absoluteFile.toURI().normalize() } else { URI.create( inputArg, ) } } /** Resolve an output filename. * If an output filename is specified directly, use it. * If only an output directory is given, add the input filename and the specified extension. * If neither a directory nor file name is given, returns null. This is used for testing. * If a path is returned and the directory doesn't already exist, it is created. */ private fun getOutputPath( cmd: CommandLine, inputFileName: String, targetExt: String?, ): Path? = getOutputPath(cmd, inputFileName, targetExt, false) private fun getOutputPath( cmd: CommandLine, inputFileName: String, targetExt: String?, forceExt: Boolean, ): Path? { val ext = if (!targetExt.isNullOrEmpty()) { FilenameUtils.EXTENSION_SEPARATOR_STR + targetExt } else { "" } var outputPath: Path? = null if (cmd.hasOption(EncodeCommandLine.OUTPUT_FILE_ARG)) { outputPath = Paths.get(cmd.getOptionValue(EncodeCommandLine.OUTPUT_FILE_ARG)) } else { val outputDir = cmd.getOptionValue(EncodeCommandLine.OUTPUT_DIR_ARG, "./") // Get the file basename without extension. The input may be a local path or a URI (for // pmtiles) val inputURI = getInputURI(inputFileName) if (inputURI.path == null) { logger.error("Unable to determine input filename from '{}'", inputFileName) return null } var baseName: String? try { val inputPath = Paths.get(inputURI.path) baseName = FilenameUtils.getBaseName(inputPath.fileName.toString()) } catch (ignored: InvalidPathException) { // Windows can't handle getting the path part of a file URI baseName = FilenameUtils.getBaseName(inputFileName) } outputPath = Paths.get(outputDir, baseName + ext) } if (outputPath != null) { if (forceExt) { outputPath = Path.of(FilenameUtils.removeExtension(outputPath.toString()) + ext) } val outputDir = outputPath.toAbsolutePath().parent if (!Files.exists(outputDir)) { try { Files.createDirectories(outputDir) } catch (ex: IOException) { logger.error("Failed to create directory '{}'", outputDir, ex) return null } } } return outputPath } private val logger: Logger = LoggerFactory.getLogger(Encode::class.java) } ================================================ FILE: java/mlt-cli/src/main/kotlin/org/maplibre/mlt/cli/EncodeCommandLine.kt ================================================ package org.maplibre.mlt.cli import org.apache.commons.cli.CommandLine import org.apache.commons.cli.Converter import org.apache.commons.cli.DefaultParser import org.apache.commons.cli.Option import org.apache.commons.cli.Options import org.apache.commons.cli.ParseException import org.apache.commons.cli.help.HelpFormatter import org.apache.commons.cli.help.TextHelpAppendable import org.apache.commons.lang3.NotImplementedException import org.apache.commons.lang3.StringUtils import org.apache.logging.log4j.Level import org.maplibre.mlt.converter.ColumnMapping import org.maplibre.mlt.converter.ColumnMappingConfig import org.maplibre.mlt.converter.encodings.fsst.FsstJni import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.IOException import java.net.URI import java.net.URISyntaxException import java.util.regex.Pattern import java.util.regex.PatternSyntaxException import java.util.stream.Collectors import java.util.stream.Stream internal object EncodeCommandLine { const val INPUT_TILE_ARG = "mvt" const val INPUT_MBTILES_ARG = "mbtiles" const val INPUT_OFFLINEDB_ARG = "offlinedb" const val INPUT_PMTILES_ARG = "pmtiles" const val OUTPUT_DIR_ARG = "dir" const val OUTPUT_FILE_ARG = "mlt" const val EXCLUDE_IDS_OPTION = "noids" const val SORT_FEATURES_OPTION = "sort-ids" const val REGEN_IDS_OPTION = "regen-ids" const val FILTER_LAYERS_OPTION = "filter-layers" const val MIN_ZOOM_OPTION = "minzoom" const val MAX_ZOOM_OPTION = "maxzoom" const val FILTER_LAYERS_INVERT_OPTION = "filter-layers-invert" const val INCLUDE_METADATA_OPTION = "metadata" const val ALLOW_COERCE_OPTION = "coerce-mismatch" const val ALLOW_ELISION_OPTION = "elide-mismatch" const val FASTPFOR_ENCODING_OPTION = "enable-fastpfor" const val FSST_ENCODING_OPTION = "enable-fsst" const val FSST_NATIVE_ENCODING_OPTION = "enable-fsst-native" const val COLUMN_MAPPING_AUTO_OPTION = "colmap-auto" const val COLUMN_MAPPING_DELIM_OPTION = "colmap-delim" const val COLUMN_MAPPING_LIST_OPTION = "colmap-list" const val NO_MORTON_OPTION = "nomorton" const val PRE_TESSELLATE_OPTION = "tessellate" const val TESSELLATE_URL_OPTION = "tessellateurl" const val OUTLINE_FEATURE_TABLES_OPTION = "outlines" const val DECODE_OPTION = "decode" const val PRINT_MLT_OPTION = "printmlt" const val PRINT_MVT_OPTION = "printmvt" const val COMPARE_ALL_OPTION = "compare-all" const val COMPARE_GEOM_OPTION = "compare-geometry" const val COMPARE_PROP_OPTION = "compare-properties" const val VECTORIZED_OPTION = "vectorized" const val TIMER_OPTION = "timer" const val COMPRESS_OPTION = "compress" private const val COMPRESS_OPTION_DEFLATE = "deflate" private const val COMPRESS_OPTION_GZIP = "gzip" private const val COMPRESS_OPTION_NONE = "none" private const val COMPRESS_OPTION_BROTLI = "brotli" private const val COMPRESS_OPTION_ZSTD = "zstd" const val PARALLEL_OPTION = "parallel" const val VERBOSE_OPTION = "verbose" const val CACHE_STATS_OPTION = "cache-stats" const val CONTINUE_OPTION = "continue" const val HELP_OPTION = "help" const val SERVER_ARG = "server" internal fun getAllowedCompressions(cmd: CommandLine): Stream { val extra = if (cmd.hasOption(INPUT_PMTILES_ARG)) Stream.of(COMPRESS_OPTION_BROTLI, COMPRESS_OPTION_ZSTD) else Stream.of() return Stream.concat( Stream.of( COMPRESS_OPTION_GZIP, COMPRESS_OPTION_DEFLATE, COMPRESS_OPTION_NONE, ), extra, ) } private fun validateCompression( cmd: CommandLine, option: String?, ): Boolean = getAllowedCompressions(cmd).anyMatch { x -> x == option } fun getCommandLine(args: Array): CommandLine? { try { val options = Options() options.addOption( Option .builder() .longOpt(INPUT_TILE_ARG) .hasArgs() .argName("file") .desc("Path to the input MVT file") .required(false) .get(), ) options.addOption( Option .builder() .longOpt(INPUT_MBTILES_ARG) .hasArg(true) .argName("file") .desc("Path of the input MBTiles file.") .required(false) .get(), ) options.addOption( Option .builder() .longOpt(INPUT_PMTILES_ARG) .hasArg(true) .argName("fileOrUri") .desc("Location of the input PMTiles file.") .required(false) .get(), ) options.addOption( Option .builder() .longOpt(INPUT_OFFLINEDB_ARG) .hasArg(true) .argName("file") .desc("Path of the input offline database file.") .required(false) .get(), ) options.addOption( Option .builder() .longOpt(OUTPUT_DIR_ARG) .hasArg(true) .argName("dir") .desc( "Directory where the output is written, using the input file basename (OPTIONAL).", ).required(false) .get(), ) options.addOption( Option .builder() .longOpt(OUTPUT_FILE_ARG) .hasArg(true) .argName("file") .desc( "Filename where the output will be written. Overrides --" + OUTPUT_DIR_ARG + ".", ).required(false) .get(), ) options.addOption( Option .builder() .longOpt(EXCLUDE_IDS_OPTION) .hasArg(false) .desc("Don't include feature IDs.") .required(false) .get(), ) options.addOption( Option .builder() .longOpt(SORT_FEATURES_OPTION) .hasArg(true) .optionalArg(true) .argName("pattern") .desc( "Reorder features of matching layers (default all) by ID, for optimal encoding of ID values.", ).required(false) .get(), ) options.addOption( Option .builder() .longOpt(REGEN_IDS_OPTION) .hasArg(true) .optionalArg(true) .argName("pattern") .desc( "Re-generate ID values of matching layers (default all). " + "Sequential values are assigned for optimal encoding, when ID values have no special meaning.", ).required(false) .get(), ) options.addOption( Option .builder() .longOpt(FILTER_LAYERS_OPTION) .hasArg(true) .desc("Filter layers by regex") .required(false) .get(), ) options.addOption( Option .builder() .longOpt(FILTER_LAYERS_INVERT_OPTION) .hasArg(false) .desc("Invert the result of --" + FILTER_LAYERS_OPTION) .required(false) .get(), ) options.addOption( Option .builder() .longOpt(MIN_ZOOM_OPTION) .hasArg(true) .optionalArg(false) .argName("level") .desc( "Minimum zoom level to encode tiles. Only applies with --" + INPUT_MBTILES_ARG + " or --" + INPUT_PMTILES_ARG, ).required(false) .converter(Converter.NUMBER) .get(), ) options.addOption( Option .builder() .longOpt(MAX_ZOOM_OPTION) .hasArg(true) .optionalArg(false) .argName("level") .desc( ( "Maximum zoom level to encode tiles. Only applies with --" + INPUT_MBTILES_ARG + " or --" + INPUT_PMTILES_ARG ), ).required(false) .converter(Converter.NUMBER) .get(), ) options.addOption( Option .builder() .longOpt(ALLOW_COERCE_OPTION) .hasArg(false) .desc("Allow coercion of property values") .required(false) .get(), ) options.addOption( Option .builder() .longOpt(ALLOW_ELISION_OPTION) .hasArg(false) .desc("Allow elision of mismatched property types") .required(false) .get(), ) options.addOption( Option .builder() .longOpt(FASTPFOR_ENCODING_OPTION) .hasArg(false) .desc("Enable FastPFOR encodings of integer columns") .required(false) .get(), ) options.addOption( Option .builder() .longOpt(FSST_ENCODING_OPTION) .hasArg(false) .desc("Enable FSST encodings of string columns (Java implementation)") .required(false) .get(), ) options.addOption( Option .builder() .longOpt(FSST_NATIVE_ENCODING_OPTION) .hasArg(false) .desc( "Enable FSST encodings of string columns (Native implementation: " + (if (FsstJni.isLoaded()) "" else "Not ") + " available)", ).required(false) .get(), ) options.addOption( Option .builder() .longOpt(COLUMN_MAPPING_AUTO_OPTION) .hasArg(true) .optionalArg(false) .argName("columns") .valueSeparator(',') .desc( """ Automatic column mapping for the specified layers (Not implemented) Layer specifications may be regular expressions if surrounded by '/'. An empty set of layers applies to all layers. """.trimIndent(), ).required(false) .get(), ) options.addOption( Option .builder() .longOpt(COLUMN_MAPPING_DELIM_OPTION) .hasArg(true) .optionalArg(false) .argName("map") .desc( """ Add a separator-based column mapping: '[,...]' e.g. '[][:]name' combines 'name' and 'name:de' on all layers. """.trimIndent(), ).required(false) .get(), ) options.addOption( Option .builder() .longOpt(COLUMN_MAPPING_LIST_OPTION) .hasArg(true) .optionalArg(false) .argName("map") .desc( """ Add an explicit column mapping on the specified layers: '[,...],...' e.g. '[]name,name:de' combines 'name' and 'name:de' on all layers. """.trimIndent(), ).required(false) .get(), ) options.addOption( Option .builder() .longOpt(NO_MORTON_OPTION) .hasArg(false) .desc("Disable Morton encoding.") .required(false) .get(), ) options.addOption( Option .builder() .longOpt(PRE_TESSELLATE_OPTION) .hasArg(false) .desc("Include tessellation data in converted tiles.") .required(false) .get(), ) options.addOption( Option .builder() .longOpt(TESSELLATE_URL_OPTION) .hasArg(true) .desc("Use a tessellation server (implies --" + PRE_TESSELLATE_OPTION + ").") .required(false) .get(), ) options.addOption( Option .builder() .longOpt(OUTLINE_FEATURE_TABLES_OPTION) .hasArgs() .desc( "The feature tables for which outlines are included " + "([OPTIONAL], comma-separated, 'ALL' for all, default: none).", ).valueSeparator(',') .argName("tables") .required(false) .optionalArg(false) .get(), ) options.addOption( Option .builder() .longOpt(DECODE_OPTION) .hasArg(false) .desc( "Test decoding the tile after encoding it. " + "Only applies with --" + INPUT_TILE_ARG + ".", ).required(false) .get(), ) options.addOption( Option .builder() .longOpt(PRINT_MLT_OPTION) .hasArg(false) .desc( ( "Print the MLT tile to stdout after encoding it. " + "Only applies with --" + INPUT_TILE_ARG + "." ), ).required(false) .get(), ) options.addOption( Option .builder() .longOpt(PRINT_MVT_OPTION) .hasArg(false) .desc( "Print the round-tripped MVT tile to stdout. " + "Only applies with --" + INPUT_TILE_ARG + ".", ).required(false) .get(), ) options.addOption( Option .builder() .longOpt(COMPARE_GEOM_OPTION) .hasArg(false) .desc("Assert that geometry in the decoded tile is the same as the input tile.") .required(false) .get(), ) options.addOption( Option .builder() .longOpt(COMPARE_PROP_OPTION) .hasArg(false) .desc("Assert that properties in the decoded tile is the same as the input tile.") .required(false) .get(), ) options.addOption( Option .builder() .longOpt(COMPARE_ALL_OPTION) .hasArg(false) .desc("Equivalent to --" + COMPARE_GEOM_OPTION + " --" + COMPARE_PROP_OPTION + ".") .required(false) .get(), ) options.addOption( Option .builder() .longOpt(VECTORIZED_OPTION) .hasArg(false) .desc("Use the vectorized decoding path.") .required(false) .deprecated() .get(), ) options.addOption( Option .builder() .longOpt(TIMER_OPTION) .hasArg(false) .desc("Print the time it takes, in ms, to decode a tile.") .required(false) .get(), ) options.addOption( Option .builder() .longOpt(COMPRESS_OPTION) .hasArg(true) .argName("algorithm") .desc( "Compress tile data with one of 'deflate', 'gzip', or 'none'. " + "Only applies with --" + INPUT_MBTILES_ARG + ", --" + INPUT_PMTILES_ARG + " or --" + INPUT_OFFLINEDB_ARG + "." + " Default: none for MBTiles and offline database, keep existing for PMTiles.", ).required(false) .get(), ) options.addOption( Option .builder() .option("j") .longOpt(PARALLEL_OPTION) .hasArg(true) .optionalArg(true) .argName("threads") .desc( "Enable parallel encoding of tiles. Only applies with --" + INPUT_MBTILES_ARG + ", --" + INPUT_OFFLINEDB_ARG + ", or --" + INPUT_PMTILES_ARG, ).required(false) .converter(Converter.NUMBER) .get(), ) options.addOption( Option .builder() .longOpt(CONTINUE_OPTION) .hasArg(false) .desc("Continue on error when possible") .required(false) .get(), ) options.addOption( Option .builder() .option("v") .longOpt(VERBOSE_OPTION) .hasArg(true) .optionalArg(true) .argName("level") .desc( "Select output verbosity for status and information on stderr. " + "Optionally specify a level: off, fatal, error, warn, info, debug, trace. " + "Default is info, or debug if --" + VERBOSE_OPTION + " is specified without a level.", ).required(false) .get(), ) options.addOption( Option .builder() .longOpt(CACHE_STATS_OPTION) .hasArg(false) .desc( "Show cache stats. Implies verbose output. Only applies with --" + INPUT_PMTILES_ARG, ).required(false) .get(), ) options.addOption( Option .builder() .option("h") .longOpt(HELP_OPTION) .hasArg(false) .desc("Show this output.") .required(false) .get(), ) options.addOption( Option .builder() .longOpt(SERVER_ARG) .hasArg(true) .optionalArg(true) .argName("port") .desc("Start encoding server") .required(false) .get(), ) val cmd = DefaultParser().parse(options, args) val tessellateSource = cmd.getOptionValue(TESSELLATE_URL_OPTION, null as String?) if (tessellateSource != null) { // throw if it's not a valid URI URI(tessellateSource) } val filterRegex = cmd.getOptionValue(FILTER_LAYERS_OPTION, null as String?) if (filterRegex != null) { // throw if it's not a valid regex Pattern.compile(filterRegex) } val compressOption = cmd.getOptionValue(COMPRESS_OPTION, COMPRESS_OPTION_NONE) if (cmd.hasOption(SERVER_ARG)) { return cmd } else if (cmd.options.size == 0 || cmd.hasOption(HELP_OPTION)) { printHelp(options) } else if (Stream .of( cmd.hasOption(INPUT_TILE_ARG), cmd.hasOption(INPUT_MBTILES_ARG), cmd.hasOption(INPUT_OFFLINEDB_ARG), cmd.hasOption(INPUT_PMTILES_ARG), ).filter { x: Boolean? -> x!! } .count() != 1L ) { logger.error( "Exactly one of --{}, --{}, --{}, or --{} must be used", INPUT_TILE_ARG, INPUT_MBTILES_ARG, INPUT_OFFLINEDB_ARG, INPUT_PMTILES_ARG, ) } else if (cmd.hasOption(OUTPUT_FILE_ARG) && cmd.hasOption(OUTPUT_DIR_ARG)) { logger.error( "Cannot specify both --{} and --{} options.", OUTPUT_FILE_ARG, OUTPUT_DIR_ARG, ) } else if (!validateCompression(cmd, compressOption)) { logger.error( "Not a valid compression type: '{}'. Valid options are: {}", compressOption, getAllowedCompressions(cmd).collect(Collectors.joining(", ")), ) } else { return cmd } } catch (ex: IOException) { logger.error("Failed to parse command line arguments", ex) } catch (ex: ParseException) { logger.error("Failed to parse command line arguments", ex) } catch (ex: URISyntaxException) { logger.error("Invalid tessellation URL", ex) } return null } private fun printHelp(options: Options?) { val autoUsage = true val header = "Convert an MVT tile or a container file containing MVT tiles to MLT format.\n" val footer = "\nExample usages:\n" + " Encode a single tile:\n" + " encode --mvt input.mvt --mlt output.mlt\n" + " Encode all tiles in an MBTiles file, with compression and parallel encoding:\n" + " encode --mbtiles input.mbtiles --dir output_dir --compress gzip -j\n" + " Start an encoding server on port 8080:\n" + " encode --server 8080\n\n" + "Environment variables:\n" + " " + ENV_TILE_LOG_INTERVAL + ": Number of tiles between status log messages (default: 10000)\n" + " " + ENV_COMPRESSION_RATIO_THRESHOLD + ": Minimum compression ratio to apply compression (default: " + DEFAULT_COMPRESSION_RATIO_THRESHOLD + ")\n" + " Only applies with --" + INPUT_MBTILES_ARG + ".\n" + " " + ENV_COMPRESSION_FIXED_THRESHOLD + ": Minimum savings in bytes for a tile to be compressed (default: " + " " + DEFAULT_COMPRESSION_FIXED_THRESHOLD + ")\n" + " Only applies with --" + INPUT_MBTILES_ARG + ".\n" + " " + ENV_CACHE_MAX + ": Maximum cache size in bytes. (default: " + DEFAULT_CACHE_MAX + ")\n" + " " + ENV_CACHE_MAX_HEAP_PERCENT + ": Maximum cache size as a percentage of maximum heap size.\n" + " Overrides " + ENV_CACHE_MAX + " if non-zero. (default: " + DEFAULT_CACHE_MAX_HEAP_PERCENT + ")\n" + " " + ENV_CACHE_EXPIRE + ": Cache expiration duration after access, in ISO-8601 format (e.g. P1.2S)\n" + " or plain (e.g., 1.2s). (default: " + DEFAULT_CACHE_EXPIRE.toString() + ")\n" + " Zero causes cache entries to be evicted only due to size.\n" + " " + ENV_CACHE_BLOCK_SIZE + ": If zero, individual tiles will be cached. If non-zero, the cache will\n" + " store aligned blocks of that size. (default: " + DEFAULT_CACHE_BLOCK_SIZE.toString() + ")\n" + " " + ENV_CACHE_AVERAGE_WEIGHT + ": Average size of tiles in bytes. (default: " + DEFAULT_CACHE_AVERAGE_WEIGHT + ")\n" + " Zero disables initial cache size estimation.\n" + " " + ENV_MAX_TILE_TRACK_SIZE + ": Maximum size of PMTiles tiles to track for duplicate detection. (default: " + DEFAULT_MAX_TILE_TRACK_SIZE + ")\n" + " All cache options only apply with --" + INPUT_PMTILES_ARG + ".\n" val target = TextHelpAppendable(System.err) val widthStr = System.getenv("COLUMNS") if (widthStr != null) { try { target.textStyleBuilder.setMaxWidth(widthStr.toInt()) } catch (ignore: NumberFormatException) { } } val formatter = HelpFormatter .builder() .setShowSince(false) .setHelpAppendable(target) .get() formatter.printHelp(Encode::class.java.name, header, options, null, autoUsage) // Passing this as the footer to `printHelp` causes it to be formatted incorrectly. target.append(footer) } // matches a layer discriminator, [] = all, [regex] = match a regex private const val LAYER_PATTERN_PREFIX = "^(?:\\[]|\\[([^]].+)])?" // A layer discriminator followed by prefix and delimiter. // Delimiter may be a pattern if surrounded by slashes. private val colMapSeparatorPattern1: Pattern = Pattern.compile(LAYER_PATTERN_PREFIX + "(.+)(/.+/)$") private val colMapSeparatorPattern2: Pattern = Pattern.compile(LAYER_PATTERN_PREFIX + "(.+)(.)$") // a layer discriminator followed by a comma-separated list of columns private val colMapListPattern: Pattern = Pattern.compile(LAYER_PATTERN_PREFIX + "(.+)$") private val colMapPatternPattern: Pattern = Pattern.compile("^/(.*)/$") private val colMapMatchAll: Pattern = Pattern.compile(".*") fun getColumnMappings(cmd: CommandLine): ColumnMappingConfig { val result = ColumnMappingConfig() if (cmd.hasOption(COLUMN_MAPPING_AUTO_OPTION)) { throw NotImplementedException("Auto column mappings are not implemented yet") } for (item in cmd.getOptionValues(COLUMN_MAPPING_LIST_OPTION) ?: arrayOf()) { val matcher = colMapListPattern.matcher(item) if (matcher.matches() && matcher.groupCount() == 2) { // matcher doesn't support multiple group matches, split them separately val layers = parseLayerPatterns(matcher.group(1) ?: "") val list = matcher.group(2) ?: "" val columnNames = list .split(",") .map { obj -> obj.trim { it <= ' ' } } .filter { !it.isEmpty() } .toList() addColumnMapping( result, layers, ColumnMapping(columnNames, true), ) } else { logger.warn( "Invalid column mapping ignored: '{}'. Expected pattern is: {}", item, colMapListPattern, ) } } for (item in cmd.getOptionValues(COLUMN_MAPPING_DELIM_OPTION) ?: arrayOf()) { val matcher = colMapSeparatorPattern1.matcher(item).let { if (it.matches()) it else colMapSeparatorPattern2.matcher(item) } if (matcher.matches() && matcher.groupCount() == 3) { // matcher doesn't support multiple group matches, split them separately val layers = parseLayerPatterns(matcher.group(1) ?: "") val prefix = parsePattern(matcher.group(2) ?: "") val delimiter = parsePattern(matcher.group(3) ?: "") addColumnMapping( result, layers, ColumnMapping(prefix, delimiter, true), ) } else { logger.warn( "Invalid column mapping ignored: '{}'. Expected pattern is: {} or {}", item, colMapSeparatorPattern1, colMapSeparatorPattern2, ) } } return result } /** Add a new item, appending the mappings to any existing mappings for the same layer pattern */ private fun addColumnMapping( result: ColumnMappingConfig, newLayer: Pattern, newMapping: ColumnMapping, ) { result.merge( newLayer, listOf(newMapping), ) { oldList, newList -> Stream.of(oldList, newList).flatMap { it.stream() }.toList() } } private fun parseLayerPatterns(pattern: String): Pattern = if (StringUtils.isBlank(pattern)) { colMapMatchAll } else { parsePattern(pattern) } private fun parsePattern(pattern: String): Pattern { val matcher = colMapPatternPattern.matcher(pattern) try { if (matcher.matches()) { // A regex surrounded by slashes, return the compiled pattern return Pattern.compile(matcher.group(1)) } else { // Just a regular string return Pattern.compile(pattern, Pattern.LITERAL) } } catch (ex: PatternSyntaxException) { logger.error("Invalid regex pattern: '{}'. Matching all input.", pattern, ex) return colMapMatchAll } } private val logger: Logger = LoggerFactory.getLogger(EncodeCommandLine::class.java) } val CommandLine.minZoom get() = getParsedOptionValue(EncodeCommandLine.MIN_ZOOM_OPTION, 0L).toInt() val CommandLine.maxZoom get() = getParsedOptionValue(EncodeCommandLine.MAX_ZOOM_OPTION, Int.MAX_VALUE.toLong()).toInt() val CommandLine.logLevel get() = ( if (hasOption(EncodeCommandLine.VERBOSE_OPTION)) { Level.toLevel(getOptionValue(EncodeCommandLine.VERBOSE_OPTION), Level.DEBUG) } else { Level.INFO } ).coerceAtLeast(if (hasOption(EncodeCommandLine.CACHE_STATS_OPTION)) Level.DEBUG else Level.OFF) val CommandLine.sortFeaturesPattern get() = if (hasOption(EncodeCommandLine.SORT_FEATURES_OPTION)) { Pattern.compile(getOptionValue(EncodeCommandLine.SORT_FEATURES_OPTION, ".*")) } else { null } val CommandLine.regenIDsPattern get() = if (hasOption(EncodeCommandLine.REGEN_IDS_OPTION)) { Pattern.compile(getOptionValue(EncodeCommandLine.REGEN_IDS_OPTION, ".*")) } else { null } val CommandLine.outlineFeatureTables get() = getOptionValues(EncodeCommandLine.OUTLINE_FEATURE_TABLES_OPTION) val CommandLine.useFSSTJava get() = hasOption(EncodeCommandLine.FSST_ENCODING_OPTION) val CommandLine.useFSSTNative get() = hasOption(EncodeCommandLine.FSST_NATIVE_ENCODING_OPTION) val CommandLine.tessellateSource get() = getOptionValue(EncodeCommandLine.TESSELLATE_URL_OPTION, null as String?) val CommandLine.tessellatePolygons get() = (tessellateSource != null) || hasOption(EncodeCommandLine.PRE_TESSELLATE_OPTION) val CommandLine.compressionType get() = getOptionValue(EncodeCommandLine.COMPRESS_OPTION, null as String?) val CommandLine.enableCoerceOnTypeMismatch get() = hasOption(EncodeCommandLine.ALLOW_COERCE_OPTION) val CommandLine.enableElideOnTypeMismatch get() = hasOption(EncodeCommandLine.ALLOW_ELISION_OPTION) val CommandLine.filterRegex get() = getOptionValue(EncodeCommandLine.FILTER_LAYERS_OPTION, null as String?) val CommandLine.filterPattern get() = filterRegex?.let(Pattern::compile) val CommandLine.filterInvert get() = hasOption(EncodeCommandLine.FILTER_LAYERS_INVERT_OPTION) val CommandLine.threadCount get() = if (hasOption(EncodeCommandLine.PARALLEL_OPTION)) { getParsedOptionValue(EncodeCommandLine.PARALLEL_OPTION, 0L).toInt() } else { 1 }.let { if (it > 0) { it } else { Runtime .getRuntime() .availableProcessors() } } ================================================ FILE: java/mlt-cli/src/main/kotlin/org/maplibre/mlt/cli/EncodeConfig.kt ================================================ package org.maplibre.mlt.cli import org.maplibre.mlt.compare.CompareHelper.CompareMode import org.maplibre.mlt.converter.ColumnMappingConfig import org.maplibre.mlt.converter.ConversionConfig import java.net.URI import java.util.regex.Pattern data class EncodeConfig( val columnMappingConfig: ColumnMappingConfig, val conversionConfig: ConversionConfig, val tessellateSource: URI?, val sortFeaturesPattern: Pattern?, val regenIDsPattern: Pattern?, val compressionType: String?, val minZoom: Int, val maxZoom: Int, val willOutput: Boolean, val willDecode: Boolean, val willPrintMLT: Boolean, val willPrintMVT: Boolean, val compareProp: Boolean, val compareGeom: Boolean, val willTime: Boolean, val taskRunner: TaskRunner, val continueOnError: Boolean, val logCacheStats: Boolean, ) { val compareMode get() = if (compareGeom && compareProp) { CompareMode.All } else if (compareGeom) { CompareMode.Geometry } else if (compareProp) { CompareMode.Properties } else { CompareMode.None } } ================================================ FILE: java/mlt-cli/src/main/kotlin/org/maplibre/mlt/cli/Environment.kt ================================================ package org.maplibre.mlt.cli import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds const val ENV_COMPRESSION_RATIO_THRESHOLD = "MLT_COMPRESSION_RATIO_THRESHOLD" const val DEFAULT_COMPRESSION_RATIO_THRESHOLD = 0.98 val compressionRatioThreshold by lazy { computeCompressionRatioThreshold() } const val ENV_COMPRESSION_FIXED_THRESHOLD = "MLT_COMPRESSION_FIXED_THRESHOLD" const val DEFAULT_COMPRESSION_FIXED_THRESHOLD = 20L val compressionFixedThreshold by lazy { computeCompressionFixedThreshold() } const val ENV_TILE_LOG_INTERVAL = "MLT_TILE_LOG_INTERVAL" const val DEFAULT_TILE_LOG_INTERVAL = 10_000L val tileLogInterval by lazy { computeTileLogInterval() } const val ENV_CACHE_MAX = "MLT_CACHE_MAX" val DEFAULT_CACHE_MAX = 64L * 1024L * 1024L val cacheMaxSize by lazy { computeCacheMaxSize() } // Default percentage is zero; a non-zero percentage will override the fixed size. const val DEFAULT_CACHE_MAX_HEAP_PERCENT = 0.0 const val ENV_CACHE_MAX_HEAP_PERCENT = "MLT_CACHE_MAX_HEAP_PERCENT" val cacheMaxHeapPercent by lazy { computeCacheMaxHeapPercent() } const val ENV_CACHE_EXPIRE = "MLT_CACHE_EXPIRE" val DEFAULT_CACHE_EXPIRE = 0.seconds val cacheExpireAfterAccess by lazy { computeCacheExpireAfterAccess() } const val ENV_THREAD_QUEUE_SIZE = "MLT_THREAD_QUEUE_SIZE" const val DEFAULT_THREAD_QUEUE_SIZE = 10_000 val threadQueueSize by lazy { computeThreadQueueSize() } const val ENV_CACHE_AVERAGE_WEIGHT = "MLT_CACHE_AVERAGE_SIZE" const val DEFAULT_CACHE_AVERAGE_WEIGHT = 4 * 1024 val cacheAverageEntrySize by lazy { computeCacheAverageEntrySize() } const val ENV_CACHE_BLOCK_SIZE = "MLT_CACHE_BLOCK_SIZE" const val DEFAULT_CACHE_BLOCK_SIZE = 16 * 1024 val cacheBlockSize by lazy { computeCacheBlockSize() } const val ENV_MAX_TILE_TRACK_SIZE = "MLT_MAX_TILE_TRACK_SIZE" const val DEFAULT_MAX_TILE_TRACK_SIZE = 1024 val maxTileTrackSize by lazy { computeMaxTileTrackSize() } val defaultEnvResolver: (String) -> String? = { name -> System.getenv(name) } /** Use the provided function to resolve environment variables. */ var envResolver = defaultEnvResolver private fun resolveConfigValue( name: String, def: T, parser: (String) -> T, ): T = try { envResolver(name)?.let { parser(it) } ?: def } catch (e: Exception) { logger.warn("Failed to parse {}, using default value {}", name, def, e) def } internal fun computeCompressionRatioThreshold() = resolveConfigValue(ENV_COMPRESSION_RATIO_THRESHOLD, DEFAULT_COMPRESSION_RATIO_THRESHOLD, String::toDouble).also { if (it < 0.0) { throw IllegalArgumentException("Compression ratio threshold must be non-negative") } } internal fun computeCompressionFixedThreshold() = resolveConfigValue(ENV_COMPRESSION_FIXED_THRESHOLD, DEFAULT_COMPRESSION_FIXED_THRESHOLD, String::toLong) internal fun computeTileLogInterval() = resolveConfigValue(ENV_TILE_LOG_INTERVAL, DEFAULT_TILE_LOG_INTERVAL, String::toLong).let { // treat zero or less as "never" if (it < 1L) Long.MAX_VALUE else it } internal fun computeCacheMaxSize() = resolveConfigValue(ENV_CACHE_MAX, DEFAULT_CACHE_MAX, String::toLong).let { if (it > 0L) it else throw IllegalArgumentException("Cache maximum size must be positive") } internal fun computeCacheMaxHeapPercent(): Double = resolveConfigValue(ENV_CACHE_MAX_HEAP_PERCENT, DEFAULT_CACHE_MAX_HEAP_PERCENT, String::toDouble).also { if (!it.isFinite() || !(0.0 <= it && it < 100.0)) { throw IllegalArgumentException("Cache max heap percent must be between 0 and 100") } } internal fun computeCacheExpireAfterAccess() = resolveConfigValue(ENV_CACHE_EXPIRE, DEFAULT_CACHE_EXPIRE, Duration::parse).also { if (it.isNegative()) { throw IllegalArgumentException("Cache expire duration must be non-negative") } } internal fun computeThreadQueueSize() = resolveConfigValue(ENV_THREAD_QUEUE_SIZE, DEFAULT_THREAD_QUEUE_SIZE, String::toInt).also { if (it < 1) { throw IllegalArgumentException("Thread queue size must be positive") } } internal fun computeCacheAverageEntrySize() = resolveConfigValue(ENV_CACHE_AVERAGE_WEIGHT, DEFAULT_CACHE_AVERAGE_WEIGHT, String::toInt).also { if (it < 0) { throw IllegalArgumentException("Cache average weight must not be negative") } } internal fun computeCacheBlockSize() = resolveConfigValue(ENV_CACHE_BLOCK_SIZE, DEFAULT_CACHE_BLOCK_SIZE, String::toInt).also { if (it < 0) { throw IllegalArgumentException("Cache block size must not be negative") } } internal fun computeMaxTileTrackSize() = resolveConfigValue(ENV_MAX_TILE_TRACK_SIZE, DEFAULT_MAX_TILE_TRACK_SIZE, String::toInt).let { it.coerceAtLeast(0) } ================================================ FILE: java/mlt-cli/src/main/kotlin/org/maplibre/mlt/cli/Json.kt ================================================ package org.maplibre.mlt.cli import org.maplibre.mlt.data.MapLibreTile import org.maplibre.mlt.data.MapboxVectorTile import org.maplibre.mlt.json.Json fun MapboxVectorTile.toJson(pretty: Boolean = true): String = Json.toJson(this, pretty) fun MapLibreTile.toJson(pretty: Boolean = true): String = Json.toJson(this, pretty) ================================================ FILE: java/mlt-cli/src/main/kotlin/org/maplibre/mlt/cli/Logging.kt ================================================ package org.maplibre.mlt.cli import org.slf4j.LoggerFactory import org.slf4j.MarkerFactory import java.text.DecimalFormat import kotlin.math.log10 import kotlin.math.pow /** Logger class for free functions */ private class CommonLogger { private constructor() } internal val logger = LoggerFactory.getLogger(CommonLogger::class.java) internal val readMarker = MarkerFactory.getMarker("READ") internal val writeMarker = MarkerFactory.getMarker("WRITE") internal val cacheMarker = MarkerFactory.getMarker("CACHE") internal val compressMarker = MarkerFactory.getMarker("COMPRESSION") internal val colMapMarker = MarkerFactory.getMarker("COLMAP") private val sizeUnits = arrayOf("B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB") // org.apache.commons.io.FileUtils.byteCountToDisplaySize does this, but always rounds down to GB fun formatSize(size: Long): String { if (size <= 0) return "0" val digitGroups = Math.floor((log10(size.toDouble()) / log10(1024.0))) val formatter = DecimalFormat("#,##0.#") return formatter.format(size / 1024.0.pow(digitGroups)) + " " + sizeUnits[digitGroups.toInt()] } fun formatNanosecDuration(nanos: Double) = when (nanos) { in 1e9..Double.MAX_VALUE -> String.format("%.2fs", nanos / 1e9) in 1e6..1e9 -> String.format("%.2fms", nanos / 1e6) in 1e3..1e6 -> String.format("%.2fus", nanos / 1e3) in Double.MIN_VALUE..1e3 -> String.format("%.2fns", nanos) else -> "0" } ================================================ FILE: java/mlt-cli/src/main/kotlin/org/maplibre/mlt/cli/MBTiles.kt ================================================ package org.maplibre.mlt.cli import org.apache.commons.lang3.mutable.MutableBoolean import org.imintel.mbtiles4j.MBTilesReadException import org.imintel.mbtiles4j.MBTilesReader import org.imintel.mbtiles4j.MBTilesWriteException import org.imintel.mbtiles4j.MBTilesWriter import org.imintel.mbtiles4j.Tile import org.imintel.mbtiles4j.model.MetadataEntry import org.maplibre.mlt.converter.MltConverter import org.maplibre.mlt.metadata.tileset.MltMetadata import java.io.ByteArrayInputStream import java.io.File import java.io.IOException import java.nio.file.Files import java.nio.file.Path import java.sql.Connection import java.sql.DriverManager import java.sql.SQLException import java.util.Optional import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicLong /** Encode the entire contents of an MBTile file of MVT tiles */ fun encodeMBTiles( inputMBTilesPath: String, outputPath: Path?, config: EncodeConfig, ): Boolean { var mbTilesReader: MBTilesReader? = null val success = AtomicBoolean(true) try { mbTilesReader = MBTilesReader(File(inputMBTilesPath)) // Remove any existing output file, as SQLite will add to it instead of replacing if (outputPath != null) { Files.deleteIfExists(outputPath) } // If no target file is specified, a temporary is used. // mbtiles4j blocks access to in-memory databases and built-in // temp file support, so we have to use its temp file support. // Nullable so that we can close it correctly in normal and exception scenarios. var mbTilesWriter: MBTilesWriter? = if (outputPath != null) { MBTilesWriter(outputPath.toFile()) } else { MBTilesWriter("encode") } try { // Copy metadata from the input file. // We can't set the format, see the coda. val metadata = mbTilesReader.getMetadata() // As of 1.0.6, mbtiles4j doesn't correctly handle metadata entries with SQL special // characters, so we have to handle these later, once we have direct access to the file. // mbTilesWriter.addMetadataEntry(metadata) val pbMeta = MltMetadata.TileSetMetadata() pbMeta.name = metadata.tilesetName pbMeta.attribution = metadata.attribution pbMeta.description = metadata.tilesetDescription val bounds = metadata.tilesetBounds pbMeta.bounds = listOf( bounds.left, bounds.top, bounds.right, bounds.bottom, ) val metadataJSON = MltConverter.createTilesetMetadataJSON(pbMeta) // Read everything on the main thread for simplicity. // Splitting reads by zoom level might improve performance. val tiles = mbTilesReader.getTiles() val tileCount = AtomicLong(0) val writerCapture = mbTilesWriter!! try { while (tiles.hasNext()) { val tile = tiles.next() try { if (!config.continueOnError && !success.get()) { break } val x = tile.column val y = tile.row val z = tile.zoom if (z < config.minZoom || z > config.maxZoom) { logger.trace("Skipping tile {}:{},{} outside zoom range", z, x, y) continue } val data = tile.data.readAllBytes() config.taskRunner.run( Runnable { val count = tileCount.incrementAndGet() if ((count % tileLogInterval) == 0L) { logger.trace( "Processing tile {} : {}:{},{}", count, z, x, y, ) } if (!convertTile( config, data, writerCapture, tile, ) ) { success.set(false) } }, ) } catch (ex: IllegalArgumentException) { success.set(false) logger.error( "Failed to convert tile {}:{},{}", tile.zoom, tile.column, tile.row, ex, ) } } try { config.taskRunner.shutdown() config.taskRunner.awaitTermination() } catch (ex: InterruptedException) { logger.error("Interrupted", ex) return false } if (!config.continueOnError && !success.get()) { return false } val dbFile = mbTilesWriter.close() if (outputPath == null) { dbFile.deleteOnExit() } mbTilesWriter = null // mbtiles4j doesn't support types other than png and jpg, // so we have to set the format metadata the hard way. getConnection(dbFile).use { connection -> updateMetadata(config, connection, metadata, metadataJSON) } } finally { // mbtiles4j doesn't support `AutoCloseable` tiles.close() } } finally { if (mbTilesWriter != null) { val file = mbTilesWriter.close() if (outputPath == null) { file.delete() } } } } catch (ex: MBTilesReadException) { success.set(false) logger.error("Failed to convert MBTiles file", ex) } catch (ex: IOException) { success.set(false) logger.error("Failed to convert MBTiles file", ex) } catch (ex: MBTilesWriteException) { success.set(false) logger.error("Failed to convert MBTiles file", ex) } catch (ex: SQLException) { success.set(false) logger.error("Failed to convert MBTiles file", ex) } finally { if (mbTilesReader != null) { mbTilesReader.close() } } return success.get() } private fun getConnectionString(dbFile: File) = "jdbc:sqlite:" + dbFile.absolutePath private fun getConnection(dbFile: File) = DriverManager.getConnection(getConnectionString(dbFile)) private fun updateMetadata( config: EncodeConfig, connection: Connection, metadata: MetadataEntry, metadataJSON: String, ) { logger.debug("Updating metadata") var sql = "INSERT OR REPLACE INTO metadata (name, value) VALUES (?, ?)" connection.prepareStatement(sql).use { statement -> for (entry in metadata.requiredKeyValuePairs) { statement.setString(1, entry.key) statement.setString(2, entry.value) statement.addBatch() } for (entry in metadata.customKeyValuePairs) { statement.setString(1, entry.key) statement.setString(2, entry.value) statement.addBatch() } logger.trace("Setting tile MIME type to '{}'", MBTILES_METADATA_MIME_TYPE) statement.setString(1, "format") statement.setString(2, MBTILES_METADATA_MIME_TYPE) statement.addBatch() // Put the global metadata in a custom metadata key. // Could also be in a custom key within the standard `json` entry... logger.trace("Adding tileset metadata JSON: {}", metadataJSON) statement.setString(1, "mln-json") statement.setString(2, metadataJSON) statement.addBatch() statement.executeBatch() } vacuumDatabase(connection) } private fun convertTile( config: EncodeConfig, data: ByteArray, writerCapture: MBTilesWriter, tile: Tile, ): Boolean { try { val x = tile.column val y = tile.row val z = tile.zoom val srcTileData = decompress(ByteArrayInputStream(data)) val didCompress = MutableBoolean(false) val tileData = convertTile( x.toLong(), y.toLong(), z, srcTileData, config, Optional.of(compressionRatioThreshold), Optional.of(compressionFixedThreshold), didCompress, ) if (tileData != null) { synchronized(writerCapture) { writerCapture.addTile(tileData, z.toLong(), x.toLong(), y.toLong()) } } return true } catch (ex: IOException) { logger.error( "Failed to convert tile {}:{},{}", tile.zoom, tile.column, tile.row, ex, ) } catch (ex: MBTilesWriteException) { logger.error( "Failed to convert tile {}:{},{}", tile.zoom, tile.column, tile.row, ex, ) } return false } ================================================ FILE: java/mlt-cli/src/main/kotlin/org/maplibre/mlt/cli/OfflineDB.kt ================================================ package org.maplibre.mlt.cli import com.google.gson.Gson import com.google.gson.JsonObject import com.google.gson.JsonSyntaxException import org.apache.commons.lang3.mutable.MutableBoolean import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.File import java.io.IOException import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardCopyOption import java.sql.Connection import java.sql.DriverManager import java.sql.PreparedStatement import java.sql.SQLException import java.util.Optional import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicLong /** Encode the MVT tiles in an offline database file */ fun encodeOfflineDB( inputPath: Path, outputPath: Path?, config: EncodeConfig, ): Boolean { // Start with a copy of the source file so we don't have to rebuild the complex schema var outputPath = outputPath try { if (outputPath == null) { val tempFile = File.createTempFile("encode-", "-db") outputPath = tempFile.toPath() tempFile.deleteOnExit() } logger.debug("Copying source file {} to {}", inputPath, outputPath) Files.copy( inputPath, outputPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES, ) } catch (ex: IOException) { logger.error("Failed to create target file", ex) return false } Class.forName("org.sqlite.JDBC") val srcConnectionString = "jdbc:sqlite:" + inputPath.toAbsolutePath() val dstConnectionString = "jdbc:sqlite:" + outputPath.toAbsolutePath() val updateSql = "UPDATE tiles SET data = ?, compressed = ? WHERE id = ?" val success = AtomicBoolean(true) try { DriverManager.getConnection(srcConnectionString).use { srcConnection -> DriverManager.getConnection(dstConnectionString).use { dstConnection -> srcConnection.createStatement().use { iterateStatement -> iterateStatement.executeQuery("SELECT * FROM tiles").use { tileResults -> dstConnection.prepareStatement(updateSql).use { updateStatement -> val tileCount = AtomicLong(0) while (tileResults.next()) { if (!config.continueOnError && !success.get()) { break } // Read on the main thread. Could be split by zoom level, etc., if needed. val uniqueID = tileResults.getLong("id") val x = tileResults.getInt("x") val y = tileResults.getInt("y") val z = tileResults.getInt("z") val data = tileResults.getBinaryStream("data").readAllBytes() config.taskRunner.run( { val count = tileCount.incrementAndGet() if ((count % tileLogInterval) == 0L) { logger.debug( "Processing tile {} : {}:{},{}", count, z, x, y, ) } if (!convertTile( config, z, x, y, data, uniqueID, updateStatement, ) ) { success.set(false) } }, ) } try { config.taskRunner.shutdown() config.taskRunner.awaitTermination() } catch (ex: InterruptedException) { logger.error("Interrupted", ex) return false } } } } if (!config.continueOnError && !success.get()) { return false } updateMetadata(config, dstConnection) vacuumDatabase(dstConnection) } } } catch (ex: SQLException) { logger.error("Offline Database conversion failed", ex) return false } catch (ex: IOException) { logger.error("Offline Database conversion failed", ex) return false } return success.get() } private fun updateMetadata( config: EncodeConfig, dstConnection: Connection, ) { val metadataKind = 2 // `mbgl::Resource::Kind::Source` val metadataQuerySQL = "SELECT id,data FROM resources WHERE kind = ?" val metadataUpdateSQL = "UPDATE resources SET data = ?, compressed = ? WHERE id = ?" dstConnection.prepareStatement(metadataQuerySQL).use { queryStatement -> dstConnection.prepareStatement(metadataUpdateSQL).use { updateStatement -> queryStatement.setInt(1, metadataKind) val metadataResults = queryStatement.executeQuery() while (metadataResults.next()) { val uniqueID = metadataResults.getLong("id") var data: ByteArray? try { data = decompress(metadataResults.getBinaryStream("data")) } catch (ignore: IOException) { logger.warn("Failed to decompress Source resource '{}', skipping", uniqueID) continue } catch (ignore: IllegalStateException) { logger.warn("Failed to decompress Source resource '{}', skipping", uniqueID) continue } // Parse JSON var jsonString = String(data, StandardCharsets.UTF_8) val json: JsonObject try { json = Gson().fromJson(jsonString, JsonObject::class.java) } catch (ex: JsonSyntaxException) { logger.warn("Source resource '{}' is not JSON, skipping", uniqueID) continue } // Update the format field json.addProperty("format", MBTILES_METADATA_MIME_TYPE) // Re-serialize jsonString = json.toString() // Re-compress val compressed: Boolean ByteArrayOutputStream().use { outputStream -> createCompressStream( outputStream, config.compressionType, ).use { compressStream -> compressed = (compressStream !== outputStream) compressStream.write(jsonString.toByteArray(StandardCharsets.UTF_8)) } data = outputStream.toByteArray() } // Update the database updateStatement.setBytes(1, data) updateStatement.setBoolean(2, compressed) updateStatement.setLong(3, uniqueID) updateStatement.execute() logger.debug( "Updated source JSON format to '{}'", MBTILES_METADATA_MIME_TYPE, ) } } } } private fun convertTile( config: EncodeConfig, z: Int, x: Int, y: Int, data: ByteArray, uniqueID: Long, updateStatement: PreparedStatement, ): Boolean { logger.trace("Converting tile {}: {},{}", z, x, y) val srcTileData: ByteArray? try { srcTileData = decompress(ByteArrayInputStream(data)) } catch (ex: IOException) { logger.error("Failed to decompress tile '{}'", uniqueID, ex) return false } catch (ex: IllegalStateException) { logger.error("Failed to decompress tile '{}'", uniqueID, ex) return false } val didCompress = MutableBoolean(false) val tileData = convertTile( x.toLong(), y.toLong(), z, srcTileData, config, Optional.of(compressionRatioThreshold), Optional.of(compressionFixedThreshold), didCompress, ) if (tileData != null) { try { // Parallel writes are possible, but only by creating a separate connection for each thread synchronized(updateStatement) { updateStatement.setBytes(1, tileData) updateStatement.setBoolean(2, didCompress.booleanValue()) updateStatement.setLong(3, uniqueID) updateStatement.execute() } return true } catch (ex: SQLException) { logger.error("Failed to convert tile '{}'", uniqueID, ex) } } return false } ================================================ FILE: java/mlt-cli/src/main/kotlin/org/maplibre/mlt/cli/PMTiles.kt ================================================ package org.maplibre.mlt.cli import com.github.benmanes.caffeine.cache.RemovalCause import com.github.benmanes.caffeine.cache.Scheduler import com.onthegomap.planetiler.archive.TileArchiveMetadata import com.onthegomap.planetiler.archive.TileArchiveWriter import com.onthegomap.planetiler.archive.TileCompression import com.onthegomap.planetiler.archive.TileEncodingResult import com.onthegomap.planetiler.archive.TileFormat import com.onthegomap.planetiler.archive.WriteableTileArchive import com.onthegomap.planetiler.geo.TileCoord import com.onthegomap.planetiler.pmtiles.Pmtiles import com.onthegomap.planetiler.pmtiles.Pmtiles.TileType import com.onthegomap.planetiler.pmtiles.WriteablePmtiles import io.tileverse.cache.CacheManager import io.tileverse.cache.CaffeineCache import io.tileverse.cache.CaffeineCacheManager import io.tileverse.io.ByteRange import io.tileverse.rangereader.AbstractRangeReader import io.tileverse.rangereader.RangeReader import io.tileverse.rangereader.RangeReaderFactory import io.tileverse.rangereader.cache.CachingRangeReader import org.apache.commons.lang3.ArrayUtils import org.apache.commons.lang3.mutable.MutableBoolean import java.io.ByteArrayInputStream import java.io.IOException import java.lang.ref.WeakReference import java.net.URI import java.nio.ByteBuffer import java.nio.file.Path import java.util.Optional import java.util.OptionalLong import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.RejectedExecutionException import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicLong import kotlin.math.max import kotlin.math.min import kotlin.time.toJavaDuration /** Encode the MVT tiles in a PMTiles file */ fun encodePMTiles( inputURI: URI, outputPath: Path, config: EncodeConfig, ): Boolean { val cacheManager = getCacheManager(config) return RangeReaderFactory.create(inputURI).use { rawReader -> logger.debug("Opened '{}' for reading", inputURI) getCachingReader(rawReader, cacheManager).use { cachingReader -> val adapter = getReaderAdapter(cachingReader) ReadablePmtiles(adapter) .use { pmTilesReader -> WriteablePmtiles.newWriteToFile(outputPath).use { writer -> logger.debug("Opened '{}' for writing", outputPath) encodePMTiles(inputURI, pmTilesReader, writer, outputPath, config) } }.also { if (config.logCacheStats) { rangeReaderCache.get()?.also(::logCacheStats) } } } } } // To have any effect on the cache size limit, we have to entirely replace the cache setup. // See `io.tileverse.rangereader.cache.RangeReaderCache.buildSharedCache` private fun getCacheManager(config: EncodeConfig) = CaffeineCacheManager().also { // `RangeReaderCache.SHARED_CACHE_NAME` is private val cacheName = "tileverse-rangereader-cache" val maxMemory = Runtime.getRuntime().maxMemory() val cacheSize = if (cacheMaxHeapPercent > 0) { (cacheMaxHeapPercent / 100 * maxMemory).toLong() } else { cacheMaxSize } logger.debug( "Initializing cache with max size {}, max age {}", formatSize(cacheSize), cacheExpireAfterAccess, ) rangeReaderCache = WeakReference( it.getCache(cacheName, { CaffeineCache .newBuilder() .maximumWeight(cacheSize) .weigher(::weigh) .scheduler(Scheduler.systemScheduler()) .also { if (cacheAverageEntrySize > 0) { it.averageWeight(weigh(cacheAverageEntrySize)) } if (cacheExpireAfterAccess.isPositive()) { it.expireAfterAccess(cacheExpireAfterAccess.toJavaDuration()) } if (config.logCacheStats) { it.recordStats() it.removalListener(::onRemoval) } }.build() }), ) } private var rangeReaderCache: WeakReference> = WeakReference(null) private fun getCachingReader( rawReader: RangeReader, cacheManager: CacheManager, ) = CachingRangeReader .builder(rawReader) .cacheManager(cacheManager) .withoutHeaderBuffer() .blockSize(cacheBlockSize) .build() private fun logCacheStats(cache: CaffeineCache) { val stats = cache.stats() val runtime = Runtime.getRuntime() val usedMem = runtime.totalMemory() - runtime.freeMemory() val freeMem = runtime.maxMemory() - usedMem logger.debug( cacheMarker, "Cache: entries={} hits={} ({}%) misses={} averageLoadTime={} evictions={} evictionWeight={} free mem={}", String.format("%,d", stats.entryCount), String.format("%,d", stats.hitCount), String.format("%.1f", 100 * stats.hitRate), String.format("%,d", stats.missCount), formatNanosecDuration(stats.averageLoadTime), String.format("%,d", stats.evictionCount), formatSize(stats.evictionWeight), formatSize(freeMem), ) } private fun getReaderAdapter(reader: AbstractRangeReader) = object : ReadablePmtiles.DataReader { override fun read( offset: Long, length: Int, ) = reader.readRange(offset, length).array() } private fun weigh( key: ByteRange, value: ByteBuffer, ) = weigh(value.capacity()) private fun weigh(size: Int): Int { // Approximate overhead of the key record (long + int + object header) val keyWeight = 24 // Value weight: object header + int field + the actual data in the ByteBuffer val valueWeight = 16 + size return keyWeight + valueWeight } private fun onRemoval( key: ByteRange?, value: ByteBuffer?, cause: RemovalCause, ) { if (logger.isTraceEnabled && cause != RemovalCause.EXPLICIT) { logger.trace(cacheMarker, "Evicted {}, {}, cause: {}", value, key, cause) } } private fun encodePMTiles( inputURI: URI, reader: ReadablePmtiles, writer: WriteablePmtiles, outputPath: Path, config: EncodeConfig, ): Boolean { val timer = if (config.willTime) Timer() else null val header = reader.header if (logger.isDebugEnabled) { val tilesPresentPct = if (header.numAddressedTiles > 0) { String.format( " (%.1f%%)", 100.0 * header.numTileContents / header.numAddressedTiles, ) } else { "" } logger.debug( "PMTiles: version={}, tileType={}, tileCompression={}, minZoom={}, maxZoom={}, numAddressedTiles={}, numTileContents={}{}", header.specVersion, header.tileType, header.tileCompression, header.minZoom, header.maxZoom, String.format("%,d", header.numAddressedTiles), String.format("%,d", header.numTileContents), tilesPresentPct, ) } if (header.tileType != TileType.MVT) { logger.error( "Input PMTiles tile type is {}, expected {}", header.tileType, TileType.MVT, ) return false } // If a compression type is given (including none) try to use that, otherwise // use the source compression type mapped to supported types. val targetCompressType = if (config.compressionType == null) { toTileCompression(header.tileCompression) } else { TileCompression.fromId(config.compressionType) } val minZoom = max(header.minZoom.toInt(), config.minZoom) val maxZoom = min(header.maxZoom.toInt(), config.maxZoom) // re-create the config with the resolved compression type val targetConfig = config.copy(compressionType = toCompressionOption(targetCompressType)) val newMetadata = updateMetadata(reader.metadata(), minZoom, maxZoom, targetCompressType) val state = ConversionState(targetConfig) try { writer.newTileWriter().use { tileWriter -> // The root task which creates other tasks for converting each tile coordinate range. // Completes when the directory has been fully read and all tasks have been created. config.taskRunner.run( { processTiles( reader.getTileCoordRanges(minZoom, maxZoom), reader, tileWriter, config.taskRunner, config, state, ) config.taskRunner.shutdown() }, ) try { // Wait for all tasks to finish config.taskRunner.awaitTermination() } catch (ex: InterruptedException) { logger.error("Interrupted", ex) state.success.set(false) } if (state.success.get()) { logger.debug("Finalizing PMTiles file") writer.finish(newMetadata) } timer?.stop("PMTiles") return state.success.get() } } catch (ex: IOException) { logger.error("PMTiles conversion failed", ex) return false } } private fun updateMetadata( oldMetadata: TileArchiveMetadata, minZoom: Int, maxZoom: Int, targetCompressType: TileCompression?, ): TileArchiveMetadata = TileArchiveMetadata( oldMetadata.name(), oldMetadata.description(), oldMetadata.attribution(), oldMetadata.version(), oldMetadata.type(), TileFormat.MLT, oldMetadata.bounds(), oldMetadata.center(), minZoom, maxZoom, oldMetadata.json(), oldMetadata.others(), targetCompressType, ) private fun processTiles( tileSeq: Sequence, reader: ReadablePmtiles, tileWriter: WriteableTileArchive.TileWriter, taskRunner: TaskRunner, config: EncodeConfig, state: ConversionState, ) { tileSeq.forEach { tileRange -> if (state.encodeConfig.continueOnError || state.success.get()) { state.totalTileCount.addAndGet(tileRange.tileCount.toLong()) try { taskRunner.run( { if (!processTileRange(tileRange, reader, tileWriter, config, state)) { state.success.set(false) } }, ) } catch (ignored: RejectedExecutionException) { // This indicates the thread pool has been shut down due to an error } } } state.directoryComplete.set(true) logger.debug( "Directory read complete. Processing {} total tiles.", String.format("%,d", state.totalTileCount.get()), ) } /** Convert a range of tile coordinates which have the same contents */ private fun processTileRange( tileRef: ReadablePmtiles.TileCoordRange, reader: ReadablePmtiles, writer: WriteableTileArchive.TileWriter, config: EncodeConfig, state: ConversionState, ): Boolean { val tileCoords = tileRef.tileCoords val firstTileCoord = tileCoords.first() val tileLabel = getTileLabel(firstTileCoord) val prevTileCount = state.tilesProcessed.getAndAdd(tileCoords.size.toLong()) val curTileCount = prevTileCount + tileCoords.size // Log every Nth tile at debug level if (logger.isDebugEnabled) { val prevBatch = prevTileCount / tileLogInterval val curBatch = curTileCount / tileLogInterval // If we're the range that crosses a log interval boundary, log the progress. if (curBatch != prevBatch || (state.directoryComplete.get() && curTileCount == state.totalTileCount.get())) { if (!state.directoryComplete.get()) { // Still fetching tile coordinates, we can't show a percentage. logger.debug( "Converted {} unique of {} tiles : {}", String.format("%,d", state.tilesConverted.get()), String.format("%,d", curTileCount), tileLabel, ) } else { val totalTiles = state.totalTileCount.get() logger.debug( "Converted {} unique of {} encountered of {} total tiles ({}%) : {}", String.format("%,d", state.tilesConverted.get()), String.format("%,d", curTileCount), String.format("%,d", totalTiles), String.format("%.1f", 100.0 * curTileCount / totalTiles), tileLabel, ) } if (config.logCacheStats) { rangeReaderCache.get()?.also(::logCacheStats) } } } if (tileRef.byteRange.length <= maxTileTrackSize) { state.offsetToHashMap.get(tileRef.byteRange.offset)?.let { tileHash -> // Already processed this input tile for a different tile coordinate range. // Pass an empty result with the same hash to the writer. Rely on // `WritablePmtiles.DeduplicatingTileArchiveWriter` to locate the previous entry and // never write this empty result, though it provides no feedback we can use to check. writeTiles(ArrayUtils.EMPTY_BYTE_ARRAY, OptionalLong.of(tileHash), tileCoords, writer) // nothing more to do for this tile range return@processTileRange true } } // Get the raw tile contents var tileData = reader.getBytes(tileRef.byteRange) // Decompress the raw tile data val compressedMVTSize = tileData.size try { tileData = decompress(ByteArrayInputStream(tileData)) } catch (ex: IOException) { logger.error("Failed to decompress tile {}", tileLabel, ex) return false } val uncompressedMVTSize = tileData.size if (logger.isTraceEnabled) { val comp = if (compressedMVTSize != uncompressedMVTSize) { ", $compressedMVTSize compressed" } else { "" } logger.trace( readMarker, "{} loaded, {} bytes{}", tileLabel, uncompressedMVTSize, comp, ) } // Convert to MLT and optionally re-compress val didCompress = MutableBoolean() val mltData = try { convertTile( firstTileCoord.x().toLong(), firstTileCoord.y().toLong(), firstTileCoord.z(), tileData, state.encodeConfig, Optional.empty(), // compress even if it increases the size Optional.empty(), didCompress, ) } catch (ex: Exception) { logger.error( "Error processing tile range {} +{}", getTileLabel(firstTileCoord), tileCoords.size - 1, ex, ) state.success.set(false) return false } state.tilesConverted.incrementAndGet() // Write the result for all the tile coordinates in the range if (mltData != null && mltData.size > 0) { val hash = OptionalLong.of(TileArchiveWriter.generateContentHash(mltData)) val existingHash = if (tileRef.byteRange.length <= maxTileTrackSize) { state.offsetToHashMap.putIfAbsent(tileRef.byteRange.offset, hash.asLong) } else { null } if (existingHash != null) { // The value was already present. This indicates that another thread processed the // same input tile since we checked above, and this thread's result will be ignored. if (hash.asLong == existingHash) { // As above, we write with no data and the same hash to reference the previous result writeTiles(ArrayUtils.EMPTY_BYTE_ARRAY, hash, tileCoords, writer) return@processTileRange true } else { logger.warn( "Hash collision for {} : {} != {}. This will cause incorrect de-duplication.", tileLabel, existingHash, hash.asLong, ) return@processTileRange false } } writeTiles(mltData, hash, tileCoords, writer) if (logger.isTraceEnabled) { val extraCoords = if (tileCoords.size > 1) { "(+ ${tileCoords.size - 1}: ${getTileLabels(tileCoords.drop(1), 10)})" } else { "" } logger.trace( writeMarker, "Writing {}{}, {} bytes{} hash {}", tileLabel, extraCoords, mltData.size, if (didCompress.isTrue) " (compressed)" else "", hash.asLong, ) } } else { logger.warn("Tile {} produced empty output, skipping", tileLabel) } return true } private fun writeTiles( tileData: ByteArray, tileHash: OptionalLong, tileCoords: Collection, writer: WriteableTileArchive.TileWriter, ) = synchronized(writer) { // The writer is not thread-safe tileCoords.forEach { tileCoord -> writer.write(TileEncodingResult(tileCoord, tileData, tileHash)) } } private fun toTileCompression(compression: Pmtiles.Compression): TileCompression = when (compression) { Pmtiles.Compression.NONE -> TileCompression.NONE Pmtiles.Compression.GZIP -> TileCompression.GZIP else -> throw IllegalArgumentException("Unsupported compression type: $compression") } private fun toCompressionOption(compression: TileCompression): String? = when (compression) { TileCompression.NONE -> null TileCompression.GZIP -> "gzip" else -> throw IllegalArgumentException("Unsupported compression type: $compression") } private data class ConversionState( // Options passed to each tile conversion val encodeConfig: EncodeConfig, // Used to track tile hashes to identify repeated tiles in separate tile ranges. // Maps from the byte offset in the source file to the hash of the generated contents. val offsetToHashMap: ConcurrentHashMap = ConcurrentHashMap(), // The number of tiles processed so far val tilesProcessed: AtomicLong = AtomicLong(0), val tilesConverted: AtomicLong = AtomicLong(0), // The total number of tiles seen in the directory so far val totalTileCount: AtomicLong = AtomicLong(0), // Whether we've finished reading the directory val directoryComplete: AtomicBoolean = AtomicBoolean(false), // Whether the conversion is successful so far val success: AtomicBoolean = AtomicBoolean(true), ) ================================================ FILE: java/mlt-cli/src/main/kotlin/org/maplibre/mlt/cli/ReadablePmtiles.kt ================================================ package org.maplibre.mlt.cli import com.onthegomap.planetiler.archive.TileArchiveMetadata import com.onthegomap.planetiler.archive.TileCompression import com.onthegomap.planetiler.archive.TileFormat import com.onthegomap.planetiler.geo.TileCoord import com.onthegomap.planetiler.pmtiles.Pmtiles import com.onthegomap.planetiler.util.Gzip import org.locationtech.jts.geom.Coordinate import java.io.IOException import java.io.UncheckedIOException /** Potentially thread-safe partial copy of Planetiler's Pmtiles reader. * This class is thread-safe as long as the provided DataReader is. * Also exposes run-length encoded tile coordinate ranges * which are not currently exposed by Planetiler's API. */ class ReadablePmtiles( private val channel: DataReader, private val closeSourceChannel: Boolean = true, ) : AutoCloseable { data class ByteRange( /** The starting position of the range */ val offset: Long, /** The number of bytes in the range */ val length: Int, ) { init { if (offset < 0) { throw IllegalArgumentException("ByteRange offset must be non-negative") } if (length < 1) { throw IllegalArgumentException("ByteRange length must be positive") } } override fun equals(other: Any?) = this === other || (other is ByteRange && offset == other.offset && length == other.length) } interface DataReader : AutoCloseable { fun read( offset: Long, length: Int, ): ByteArray override fun close() {} } private fun getBytes( start: Long, length: Long, ) = channel.read(start, Math.toIntExact(length)) fun getBytes(range: ByteRange) = getBytes(range.offset, range.length.toLong()) private fun getHeaderBytes( offset: Long, length: Long, ) = getBytes(offset, length).let { if (header.internalCompression() == Pmtiles.Compression.GZIP) { Gzip.gunzip(it) } else { it } } fun getTileRange( x: Int, y: Int, z: Int, ): ByteRange? { try { val tileId = TileCoord.ofXYZ(x, y, z).hilbertEncoded() var dirOffset = header.rootDirOffset() var dirLength = header.rootDirLength().toInt() for (depth in 0..3) { val dirBytes = getHeaderBytes(dirOffset, dirLength.toLong()) val dir = Pmtiles.directoryFromBytes(dirBytes) val entry = findTile(dir, tileId.toLong()) if (entry != null) { if (entry.runLength() > 0) { return ByteRange(header.tileDataOffset() + entry.offset(), entry.length()) } else { dirOffset = header.leafDirectoriesOffset() + entry.offset() dirLength = entry.length() } } else { return null } } } catch (e: IOException) { throw IllegalStateException("Could not get tile", e) } return null } fun getTile( x: Int, y: Int, z: Int, ) = getTileRange(x, y, z)?.let(::getBytes) override fun close() { if (closeSourceChannel) { channel.close() } } val jsonMetadata by lazy { Pmtiles.JsonMetadata.fromBytes(getHeaderBytes(header.jsonMetadataOffset, header.jsonMetadataLength)) } fun metadata(): TileArchiveMetadata { val tileCompression = when (header.tileCompression) { Pmtiles.Compression.GZIP -> TileCompression.GZIP Pmtiles.Compression.NONE -> TileCompression.NONE Pmtiles.Compression.UNKNOWN -> TileCompression.UNKNOWN } val format = when (header.tileType) { Pmtiles.TileType.MVT -> TileFormat.MVT Pmtiles.TileType.MLT -> TileFormat.MLT else -> null } try { val jsonMetadata = this.jsonMetadata val map = LinkedHashMap(jsonMetadata.otherMetadata) return TileArchiveMetadata( map.remove(TileArchiveMetadata.NAME_KEY), map.remove(TileArchiveMetadata.DESCRIPTION_KEY), map.remove(TileArchiveMetadata.ATTRIBUTION_KEY), map.remove(TileArchiveMetadata.VERSION_KEY), map.remove(TileArchiveMetadata.TYPE_KEY), format, header.bounds(), Coordinate( header.center().getX(), header.center().getY(), header.centerZoom.toDouble(), ), header.minZoom.toInt(), header.maxZoom.toInt(), TileArchiveMetadata.TileArchiveMetadataJson.create(jsonMetadata.vectorLayers), map, tileCompression, ) } catch (e: IOException) { throw UncheckedIOException(e) } } private fun readDir(entry: Pmtiles.Entry) = readDir(header.leafDirectoriesOffset() + entry.offset(), entry.length().toLong()) private fun readDir( offset: Long, length: Long, ) = Pmtiles.directoryFromBytes(getHeaderBytes(offset, length)) data class TileCoordRange( val startTileId: Long, val tileCount: Int, val byteRange: ByteRange, ) { constructor(entry: Pmtiles.Entry, header: Pmtiles.Header) : this( entry.tileId(), entry.runLength(), ByteRange(header.tileDataOffset() + entry.offset(), entry.length()), ) init { if (tileCount < 1) { throw IllegalArgumentException("tileCount must be positive") } if (byteRange.offset < 0) { throw IllegalArgumentException("byteRange offset must be non-negative") } if (byteRange.length < 1) { throw IllegalArgumentException("byteRange length must be positive") } } val tileIds get() = (startTileId until startTileId + tileCount) // Warning: this will only work on z15 or less pmtiles which planetiler creates // TODO: Will extending `hilbertDecode` to Longs solve this? val tileCoords get() = tileIds.map { TileCoord.hilbertDecode(it.toInt()) } } fun getTileCoordRanges( minZoom: Int? = null, maxZoom: Int? = null, ) = getTileCoordRanges(rootDir, minZoom?.let(::zoomStartIndex) ?: 0, maxZoom?.let(::zoomEndIndex) ?: Long.MAX_VALUE) /** Generate tile ranges for the Hilbert given tile indexes (half-closed range) * @param dir the directory to search * @param startTileIndex the first tile index to include * @param endTileIndex the first tile index to exclude * */ private fun getTileCoordRanges( dir: Iterable, startTileIndex: Long, endTileIndex: Long, ): Sequence = dir .asSequence() .flatMap { entry -> mapDirectory(entry, startTileIndex, endTileIndex, header) { getTileCoordRanges(readDir(entry), startTileIndex, endTileIndex) } } /** Zoom start index, sum of previous tile counts */ private fun zoomStartIndex(zoom: Int) = ZOOM_START_INDEX[zoom.coerceIn(0..MAX_ZOOM)] /** Zoom end index, first index of the next zoom */ private fun zoomEndIndex(zoom: Int) = ZOOM_START_INDEX[zoom.coerceIn(0..MAX_ZOOM) + 1] val header by lazy { Pmtiles.Header.fromBytes(getBytes(0, HEADER_LEN.toLong())) } private val rootDir by lazy { readDir(header.rootDirOffset(), header.rootDirLength()) } companion object { const val HEADER_LEN: Int = 127 // Pmtiles.HEADER_LEN is inaccessible const val MAX_ZOOM = 16 // Planetiler currently only supports 15 private val ZOOM_START_INDEX = buildZoomIndex() private fun buildZoomIndex(): LongArray { val indexes = LongArray(MAX_ZOOM + 2) var index = 0L (0..MAX_ZOOM + 1).forEach { indexes[it] = index val width = (1L shl it) index += width * width if (index < indexes[it]) { throw IllegalStateException("Too many zoom levels") } } return indexes } /** * Finds the relevant entry for a tileId in a list of entries. * * If there is an exact match for tileId, return that. Else if the tileId matches an entry's * tileId + runLength, return that. Else if the preceding entry is a directory (runLength = 0), * return that. Else return null. */ private fun findTile( entries: List, tileId: Long, ): Pmtiles.Entry? { var m = 0 var n = entries.size - 1 while (m <= n) { val k = (n + m) shr 1 val entry = entries.get(k) val cmp = tileId - entry.tileId() if (cmp > 0) { m = k + 1 } else if (cmp < 0) { n = k - 1 } else { return entry } } if (n >= 0) { val entry = entries.get(n) if ((entry.runLength() == 0 || tileId - entry.tileId() < entry.runLength())) { return entry } } return null } internal fun mapDirectory( entry: Pmtiles.Entry, startTileIndex: Long, endTileIndex: Long, header: Pmtiles.Header, recurse: (entry: Pmtiles.Entry) -> Sequence, ): Sequence { // Run-length zero is a directory reference if (entry.runLength() == 0) { // continue exploring this branch? if (entry.tileId() < endTileIndex) { return recurse(entry) } } else { // Run-length non-zero is a tile range var rangeEnd = entry.tileId() + entry.runLength() if (startTileIndex <= entry.tileId() && rangeEnd <= endTileIndex) { // use the full range return sequenceOf(TileCoordRange(entry, header)) } else if (entry.tileId() < endTileIndex && startTileIndex < rangeEnd) { // use a partial range val start = entry.tileId().coerceAtLeast(startTileIndex) val end = (entry.tileId() + entry.runLength()).coerceAtMost(endTileIndex) return sequenceOf( TileCoordRange( start, (end - start).toInt(), ByteRange(header.tileDataOffset() + entry.offset(), entry.length()), ), ) } } return sequenceOf() } } } ================================================ FILE: java/mlt-cli/src/main/kotlin/org/maplibre/mlt/cli/SerialTaskRunner.kt ================================================ package org.maplibre.mlt.cli class SerialTaskRunner : TaskRunner { override val threadCount: Int get() = 0 override fun run(task: Runnable?) { if (task != null) { task.run() } } override fun awaitTermination() { } override fun shutdown() {} } ================================================ FILE: java/mlt-cli/src/main/kotlin/org/maplibre/mlt/cli/Server.kt ================================================ package org.maplibre.mlt.cli import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.BufferedReader import java.io.InputStreamReader import java.net.ServerSocket import java.net.Socket class Server { fun run(port: Int) { if (isRunning(port)) { throw RuntimeException("Port $port is already in use") } startServer(port) // never returns } private fun isRunning(port: Int): Boolean { try { Socket("localhost", port).use { _ -> return true } } catch (_: Exception) { return false } } private fun startServer(port: Int) { try { ServerSocket(port).use { server -> logger.info("Server started on port {}", port) while (true) { val client = server.accept() Thread(Runnable { handleClient(client) }).start() } } } catch (ex: Exception) { logger.error("Failed to start server on port {}", port, ex) } } private fun handleClient(socket: Socket) { try { BufferedReader(InputStreamReader(socket.getInputStream())).use { `in` -> val command = `in`.readLine() if (command != null) { Encode.run( command .trim { it <= ' ' } .split("\\s+".toRegex()) .dropLastWhile { it.isEmpty() } .toTypedArray(), ) } } } catch (ex: Exception) { logger.error("Failed to handle client connection", ex) } } companion object { private val logger: Logger = LoggerFactory.getLogger(Server::class.java) } } ================================================ FILE: java/mlt-cli/src/main/kotlin/org/maplibre/mlt/cli/TaskRunner.kt ================================================ package org.maplibre.mlt.cli import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.ThreadPoolExecutor import java.util.concurrent.TimeUnit /** Simplify optional parallel operation by running tasks in a thread pool if provided, or directly if not. */ interface TaskRunner { /** Get the number of threads in use, not including the main thread */ val threadCount: Int /** Execute the given task either directly or on the given thread pool */ fun run(task: Runnable?) /** Wait for all tasks to complete. Assumes shutdown has been called. */ fun awaitTermination() /** Stop accepting new tasks */ fun shutdown() } fun createBoundedNonRejectingTaskRunner(threadCount: Int): TaskRunner { if (threadCount < 2) { return SerialTaskRunner() } // Because the main thread is also used for running tasks when the pool is saturated, // we count it as one of the threads and so reduce the pool size by one. val poolSize = threadCount - 1 // Threads are expected to be used continuously, and so don't time out. val threadTimeout = Long.MAX_VALUE // Create a thread pool with a bounded task queue that will not reject tasks when // it's full. Tasks beyond the limit will run on the calling thread, preventing // OOM from too many tasks while allowing for parallelism when the pool is available. val taskQueue = LinkedBlockingQueue(threadQueueSize * poolSize) val rejectHandler = ThreadPoolExecutor.CallerRunsPolicy() return ThreadPoolTaskRunner( ThreadPoolExecutor( poolSize, poolSize, threadTimeout, TimeUnit.SECONDS, taskQueue, rejectHandler, ), ) } ================================================ FILE: java/mlt-cli/src/main/kotlin/org/maplibre/mlt/cli/ThreadPoolTaskRunner.kt ================================================ package org.maplibre.mlt.cli import java.util.concurrent.ThreadPoolExecutor import java.util.concurrent.TimeUnit class ThreadPoolTaskRunner( private val threadPool: ThreadPoolExecutor, ) : TaskRunner { override val threadCount: Int get() = threadPool.maximumPoolSize override fun run(task: Runnable?) { if (task != null) { threadPool.execute(task) } } override fun awaitTermination() { threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS) } override fun shutdown() { threadPool.shutdown() } } ================================================ FILE: java/mlt-cli/src/main/kotlin/org/maplibre/mlt/cli/Timer.kt ================================================ package org.maplibre.mlt.cli import org.slf4j.LoggerFactory class Timer { private var startTime: Long init { startTime = System.nanoTime() } fun restart() { startTime = System.nanoTime() } val elapsedTime get() = (System.nanoTime() - startTime).toDouble() / 1_000_000 // divide by 1000000 to get milliseconds fun stop(message: String?) { logger.info("Time elapsed for {}: {} ms", message, elapsedTime) } private val logger = LoggerFactory.getLogger(Timer::class.java) } ================================================ FILE: java/mlt-cli/src/main/resources/log4j2.json ================================================ { "Configuration": { "status": "warn", "Appenders": { "Console": { "name": "Console", "target": "system_err", "PatternLayout": { "noConsoleNoAnsi": true, "LevelPatternSelector": { "PatternMatch": [ { "key": "FATAL", "pattern": "%highlight{FATAL: %msg%n%throwable}{FATAL=bright red}" }, { "key": "ERROR", "pattern": "%highlight{ERROR: %msg%n%throwable}{ERROR=red}" }, { "key": "WARN", "pattern": "%highlight{WARNING: %msg%n%throwable}{WARN=yellow}" }, { "key": "INFO", "pattern": "%msg%n%throwable" }, { "key": "DEBUG", "pattern": "%highlight{%msg%n%throwable}{DEBUG=dim white}" }, { "key": "TRACE", "pattern": "%highlight{%msg%n%throwable}{TRACE=bright black}" } ] } } } }, "Loggers": { "Root": { "AppenderRef": [ { "ref": "Console" } ] } } } } ================================================ FILE: java/mlt-cli/src/test/kotlin/org/maplibre/mlt/cli/EncodeCommandLineTest.kt ================================================ package org.maplibre.mlt.cli import org.apache.commons.cli.DefaultParser import org.apache.commons.cli.Option import org.apache.commons.cli.Options import org.apache.logging.log4j.Level import org.junit.jupiter.api.Assertions.assertArrayEquals import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test class EncodeCommandLineTest { @Test fun `getColumnMappings list`() { val options = Options() options.addOption( Option .builder() .longOpt(EncodeCommandLine.COLUMN_MAPPING_LIST_OPTION) .hasArg(true) .argName("map") .get(), ) val args = arrayOf("--${EncodeCommandLine.COLUMN_MAPPING_LIST_OPTION}", "[]name,name:de") val cmd = DefaultParser().parse(options, args) val cfg = EncodeCommandLine.getColumnMappings(cmd) assertEquals(1, cfg.size) val entry = cfg.entries.first() // empty layer list ([]) should match all assertEquals(".*", entry.key.pattern()) val mappings = entry.value assertEquals(1, mappings.size) val m = mappings[0] assertTrue(m.hasColumnNames()) assertTrue(m.columnNames.contains("name")) assertTrue(m.columnNames.contains("name:de")) } @Test fun `getColumnMappings delimited`() { val options = Options() options.addOption( Option .builder() .longOpt(EncodeCommandLine.COLUMN_MAPPING_DELIM_OPTION) .hasArg(true) .argName("map") .get(), ) val args = arrayOf("--${EncodeCommandLine.COLUMN_MAPPING_DELIM_OPTION}", "[roads]name/[:_]/") val cmd = DefaultParser().parse(options, args) val cfg = EncodeCommandLine.getColumnMappings(cmd) assertEquals(1, cfg.size) val entry = cfg.entries.first() // layer discriminator should create a literal pattern for 'roads' assertEquals("roads", entry.key.pattern()) val m = entry.value[0] assertFalse(m.hasColumnNames()) assertEquals("name", m.prefix.pattern()) assertEquals("[:_]", m.delimiter.pattern()) } @Test fun `getAllowedCompressions Pmtiles`() { val options = Options().addOption( Option .builder() .longOpt(EncodeCommandLine.INPUT_PMTILES_ARG) .hasArg(true) .get(), ) val cmd = DefaultParser().parse(options, arrayOf("--${EncodeCommandLine.INPUT_PMTILES_ARG}", "input.pmtiles")) val list = EncodeCommandLine.getAllowedCompressions(cmd).toList() // should contain the standard compressors plus brotli/zstd when pmtiles option present assertTrue(list.containsAll(listOf("gzip", "deflate", "none", "brotli", "zstd"))) } @Test fun `getAllowedCompressions not Pmtiles`() { val cmd = DefaultParser().parse(Options(), arrayOf()) val list = EncodeCommandLine.getAllowedCompressions(cmd).toList() assertTrue(list.containsAll(listOf("gzip", "deflate", "none"))) assertFalse(list.contains("brotli") || list.contains("zstd")) } /** Tests for the helper extension properties on `CommandLine` defined in `EncodeCommandLine.kt` */ @Test fun `basic parsing of helper properties`() { val args = arrayOf( "--mvt", "input.mvt", "--minzoom", "2", "--maxzoom", "10", "--parallel", "3", "--verbose", "debug", "--sort-ids", "^foo$", "--regen-ids", "bar", "--outlines", "one,two", "--enable-fsst", "--enable-fsst-native", "--tessellateurl", "http://example/", "--compress", "gzip", "--coerce-mismatch", "--elide-mismatch", "--filter-layers", "name.*", "--filter-layers-invert", ) val cmd = EncodeCommandLine.getCommandLine(args) assertNotNull(cmd) cmd!! assertEquals(2, cmd.minZoom) assertEquals(10, cmd.maxZoom) assertEquals(3, cmd.threadCount) assertEquals(Level.DEBUG, cmd.logLevel) assertEquals("^foo$", cmd.sortFeaturesPattern?.pattern()) assertEquals("bar", cmd.regenIDsPattern?.pattern()) val outlines = cmd.outlineFeatureTables assertNotNull(outlines) assertArrayEquals(arrayOf("one", "two"), outlines) assertTrue(cmd.useFSSTJava) assertTrue(cmd.useFSSTNative) assertEquals("http://example/", cmd.tessellateSource) assertTrue(cmd.tessellatePolygons) assertEquals("gzip", cmd.compressionType) assertTrue(cmd.enableCoerceOnTypeMismatch) assertTrue(cmd.enableElideOnTypeMismatch) assertEquals("name.*", cmd.filterRegex) assertEquals("name.*", cmd.filterPattern?.pattern()) assertTrue(cmd.filterInvert) } @Test fun `cache stats forces debug log level`() { val args = arrayOf( "--" + EncodeCommandLine.INPUT_PMTILES_ARG, "input.pmtiles", "--" + EncodeCommandLine.CACHE_STATS_OPTION, "--" + EncodeCommandLine.VERBOSE_OPTION, "error", ) val cmd = EncodeCommandLine.getCommandLine(args) assertNotNull(cmd) cmd!! assertEquals(Level.DEBUG, cmd.logLevel) } @Test fun `log level isn't forced otherwise`() { val args = arrayOf("--" + EncodeCommandLine.INPUT_PMTILES_ARG, "input.pmtiles", "--" + EncodeCommandLine.VERBOSE_OPTION, "error") val cmd = EncodeCommandLine.getCommandLine(args) assertNotNull(cmd) cmd!! assertEquals(Level.ERROR, cmd.logLevel) } @Test fun `thread count default when not present`() { val args = arrayOf("--mvt", "input.mvt") val cmd = EncodeCommandLine.getCommandLine(args) assertNotNull(cmd) cmd!! assertEquals(1, cmd.threadCount) } @Test fun `thread count with explicit value`() { val args = arrayOf("--mvt", "input.mvt", "--parallel", "2") val cmd = EncodeCommandLine.getCommandLine(args) assertNotNull(cmd) cmd!! assertEquals(2, cmd.threadCount) } } ================================================ FILE: java/mlt-cli/src/test/kotlin/org/maplibre/mlt/cli/EnvironmentResolverTest.kt ================================================ package org.maplibre.mlt.cli import org.apache.logging.log4j.Level import org.apache.logging.log4j.core.config.Configurator import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import kotlin.time.Duration.Companion.seconds class EnvironmentResolverTest { var savedResolver = envResolver @BeforeEach fun setUp() { savedResolver = envResolver Configurator.setLevel(logger.name, Level.ERROR) // suppress expected warning logs } @AfterEach fun tearDown() { envResolver = savedResolver } @Test fun `override compression ratio with valid value`() { envResolver = { name -> if (name == ENV_COMPRESSION_RATIO_THRESHOLD) "0.5" else null } assertEquals(0.5, computeCompressionRatioThreshold()) } @Test fun `override compression ratio with invalid negative throws`() { envResolver = { name -> if (name == ENV_COMPRESSION_RATIO_THRESHOLD) "-1" else null } assertThrows(illegalArgType) { computeCompressionRatioThreshold() }.andContains("non-negative") } /** When the parser throws (invalid format), resolveConfigValue should return the default */ @Test fun `parser throws returns default`() { Configurator.setLevel(logger.name, Level.ERROR) // suppress expected warning logs; envResolver = { name -> if (name == ENV_COMPRESSION_RATIO_THRESHOLD) "not-a-number" else null } assertEquals(DEFAULT_COMPRESSION_RATIO_THRESHOLD, computeCompressionRatioThreshold()) } @Test fun `computeCompressionFixedThreshold success`() { envResolver = { name -> if (name == ENV_COMPRESSION_FIXED_THRESHOLD) "123" else null } assertEquals(123L, computeCompressionFixedThreshold()) } @Test fun `computeCompressionFixedThreshold parser failure returns default`() { envResolver = { name -> if (name == ENV_COMPRESSION_FIXED_THRESHOLD) "bad" else null } assertEquals(DEFAULT_COMPRESSION_FIXED_THRESHOLD, computeCompressionFixedThreshold()) } @Test fun `computeTileLogInterval success`() { envResolver = { name -> if (name == ENV_TILE_LOG_INTERVAL) "12345" else null } assertEquals(12345L, computeTileLogInterval()) } @Test fun `computeTileLogInterval parser failure returns default`() { envResolver = { name -> if (name == ENV_TILE_LOG_INTERVAL) "bad" else null } assertEquals(DEFAULT_TILE_LOG_INTERVAL, computeTileLogInterval()) } @Test fun `computeTileLogInterval zero treated as never`() { envResolver = { name -> if (name == ENV_TILE_LOG_INTERVAL) "0" else null } assertEquals(Long.MAX_VALUE, computeTileLogInterval()) } @Test fun `cache expire accepts ISO duration`() { envResolver = { name -> if (name == ENV_CACHE_EXPIRE) "PT20S" else null } assertEquals(20.seconds, computeCacheExpireAfterAccess()) } @Test fun `cache expire negative throws`() { envResolver = { name -> if (name == ENV_CACHE_EXPIRE) "-PT1S" else null } assertThrows(illegalArgType) { computeCacheExpireAfterAccess() }.andContains("non-negative") } @Test fun `thread queue size positive`() { envResolver = { name -> if (name == ENV_THREAD_QUEUE_SIZE) "5" else null } assertEquals(5, computeThreadQueueSize()) } @Test fun `thread queue size invalid throws`() { envResolver = { name -> if (name == ENV_THREAD_QUEUE_SIZE) "0" else null } assertThrows(illegalArgType) { computeThreadQueueSize() }.andContains("positive") } @Test fun `cache max heap valid`() { envResolver = { name -> if (name == ENV_CACHE_MAX) "12345" else null } assertEquals(12345L, computeCacheMaxSize()) } @Test fun `cache max zero throws`() { envResolver = { name -> if (name == ENV_CACHE_MAX) "0" else null } assertThrows(illegalArgType) { computeCacheMaxSize() }.andContains("positive") } @Test fun `cache max negative throws`() { // larger than Long.MAX_VALUE envResolver = { name -> if (name == ENV_CACHE_MAX) "-1" else null } assertThrows(illegalArgType) { computeCacheMaxSize() }.andContains("positive") } @Test fun `cache max heap percent valid range accepts valid`() { envResolver = { name -> if (name == ENV_CACHE_MAX_HEAP_PERCENT) "99.9" else null } assertEquals(99.9, computeCacheMaxHeapPercent()) } @Test fun `cache max heap percent invalid throws`() { envResolver = { name -> if (name == ENV_CACHE_MAX_HEAP_PERCENT) "-1" else null } assertThrows(illegalArgType) { computeCacheMaxHeapPercent() }.andContains("between 0 and 100") envResolver = { name -> if (name == ENV_CACHE_MAX_HEAP_PERCENT) "100" else null } assertThrows(illegalArgType) { computeCacheMaxHeapPercent() }.andContains("between 0 and 100") } @Test fun `cache max heap percent detects special values`() { envResolver = { name -> if (name == ENV_CACHE_MAX_HEAP_PERCENT) "NaN" else null } assertThrows(illegalArgType) { computeCacheMaxHeapPercent() }.andContains("between 0 and 100") envResolver = { name -> if (name == ENV_CACHE_MAX_HEAP_PERCENT) "Infinity" else null } assertThrows(illegalArgType) { computeCacheMaxHeapPercent() }.andContains("between 0 and 100") envResolver = { name -> if (name == ENV_CACHE_MAX_HEAP_PERCENT) "-Infinity" else null } assertThrows(illegalArgType) { computeCacheMaxHeapPercent() }.andContains("between 0 and 100") } @Test fun `computeCacheAverageEntrySize success`() { envResolver = { name -> if (name == ENV_CACHE_AVERAGE_WEIGHT) "2048" else null } assertEquals(2048, computeCacheAverageEntrySize()) } @Test fun `computeCacheAverageEntrySize parser failure returns default`() { envResolver = { name -> if (name == ENV_CACHE_AVERAGE_WEIGHT) "x" else null } assertEquals(DEFAULT_CACHE_AVERAGE_WEIGHT, computeCacheAverageEntrySize()) } @Test fun `computeCacheAverageEntrySize negative throws`() { envResolver = { name -> if (name == ENV_CACHE_AVERAGE_WEIGHT) "-1" else null } assertThrows(illegalArgType) { computeCacheAverageEntrySize() }.andContains("not be negative") } @Test fun `computeCacheBlockSize success`() { envResolver = { name -> if (name == ENV_CACHE_BLOCK_SIZE) "2048" else null } assertEquals(2048, computeCacheBlockSize()) } @Test fun `computeCacheBlockSize parser failure returns default`() { envResolver = { name -> if (name == ENV_CACHE_BLOCK_SIZE) "x" else null } assertEquals(DEFAULT_CACHE_BLOCK_SIZE, computeCacheBlockSize()) } @Test fun `computeCacheBlockSize negative throws`() { envResolver = { name -> if (name == ENV_CACHE_BLOCK_SIZE) "-1" else null } assertThrows(illegalArgType) { computeCacheBlockSize() }.andContains("not be negative") } @Test fun `maxTileTrackSize success`() { envResolver = { name -> if (name == ENV_MAX_TILE_TRACK_SIZE) "12345" else null } assertEquals(12345, computeMaxTileTrackSize()) } @Test fun `maxTileTrackSize parser failure returns default`() { envResolver = { name -> if (name == ENV_MAX_TILE_TRACK_SIZE) "x" else null } assertEquals(DEFAULT_MAX_TILE_TRACK_SIZE, computeMaxTileTrackSize()) } @Test fun `maxTileTrackSize accepts zero and negative`() { envResolver = { name -> if (name == ENV_MAX_TILE_TRACK_SIZE) "0" else null } assertEquals(0, computeMaxTileTrackSize()) envResolver = { name -> if (name == ENV_MAX_TILE_TRACK_SIZE) "-1" else null } assertEquals(0, computeMaxTileTrackSize()) } private val illegalArgType = IllegalArgumentException::class.java } ================================================ FILE: java/mlt-cli/src/test/kotlin/org/maplibre/mlt/cli/LoggingTest.kt ================================================ package org.maplibre.mlt.cli import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test class LoggingTest { @Test fun `formatSize returns zero for non-positive`() { assertEquals("0", formatSize(0)) assertEquals("0", formatSize(-1)) } @Test fun `formatSize formats bytes correctly`() { assertEquals("1 B", formatSize(1)) assertEquals("1,023 B", formatSize(1023)) } @Test fun `formatSize formats kibibytes and beyond`() { assertEquals("1 kiB", formatSize(1024)) assertEquals("1.5 kiB", formatSize(1536)) assertEquals("1 MiB", formatSize(1024L * 1024)) assertEquals("5 GiB", formatSize(5L * 1024 * 1024 * 1024)) } @Test fun `formatNanosecDuration formats ns, us, ms, s and zero`() { // nanoseconds assertEquals("0.50ns", formatNanosecDuration(0.5)) assertEquals("1.00us", formatNanosecDuration(1e3)) assertEquals("1.00ms", formatNanosecDuration(1e6)) assertEquals("1.00s", formatNanosecDuration(1e9)) assertEquals("2.35s", formatNanosecDuration(2.345e9)) assertEquals("0", formatNanosecDuration(0.0)) } } ================================================ FILE: java/mlt-cli/src/test/kotlin/org/maplibre/mlt/cli/ReadablePmtilesMapDirectoryTest.kt ================================================ package org.maplibre.mlt.cli import com.onthegomap.planetiler.pmtiles.Pmtiles import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.mockito.kotlin.mock import org.mockito.kotlin.whenever /** Tests for directory traversal logic in `ReadablePmtiles` */ class ReadablePmtilesMapDirectoryTest { private fun mockEntry( tileId: Long, runLength: Int, offset: Long = 0, length: Int = 1, ): Pmtiles.Entry { val entry = mock() whenever(entry.tileId()).thenReturn(tileId) whenever(entry.runLength()).thenReturn(runLength) whenever(entry.offset()).thenReturn(offset) whenever(entry.length()).thenReturn(length) return entry } private fun mockHeader(tileDataOffset: Long = 0): Pmtiles.Header { val header = mock() whenever(header.tileDataOffset()).thenReturn(tileDataOffset) return header } @Test fun `directory reference entry calls recurse when tileId less than endTileIndex`() { val entry = mockEntry(tileId = 5, runLength = 0) val header = mockHeader() var recurseCalled = false val recurse: (Pmtiles.Entry) -> Sequence = { recurseCalled = true emptySequence() } val result = ReadablePmtiles.mapDirectory(entry, 0, 10, header, recurse) assertTrue(recurseCalled) assertTrue(result.toList().isEmpty()) } @Test fun `directory reference entry does not call recurse when tileId not less than endTileIndex`() { val entry = mockEntry(tileId = 10, runLength = 0) val header = mockHeader() var recurseCalled = false val recurse: (Pmtiles.Entry) -> Sequence = { recurseCalled = true emptySequence() } val result = ReadablePmtiles.mapDirectory(entry, 0, 10, header, recurse) assertFalse(recurseCalled) assertTrue(result.toList().isEmpty()) } @Test fun `tile range entry returns full range when within bounds`() { val entry = mockEntry(tileId = 2, runLength = 3, offset = 10, length = 5) val header = mockHeader(tileDataOffset = 100) val recurse: (Pmtiles.Entry) -> Sequence = { emptySequence() } val result = ReadablePmtiles.mapDirectory(entry, 0, 10, header, recurse) val ranges = result.toList() assertEquals(1, ranges.size) val range = ranges[0] assertEquals(2, range.startTileId) assertEquals(3, range.tileCount) assertEquals(ReadablePmtiles.ByteRange(110, 5), range.byteRange) } @Test fun `tile range entry returns partial range when overlapping bounds`() { val entry = mockEntry(tileId = 5, runLength = 5, offset = 20, length = 10) val header = mockHeader(tileDataOffset = 50) val recurse: (Pmtiles.Entry) -> Sequence = { emptySequence() } val result = ReadablePmtiles.mapDirectory(entry, 7, 12, header, recurse) val ranges = result.toList() assertEquals(1, ranges.size) val range = ranges[0] assertEquals(7, range.startTileId) assertEquals(3, range.tileCount) assertEquals(ReadablePmtiles.ByteRange(70, 10), range.byteRange) } @Test fun `tile range entry returns empty when out of bounds below`() { val entry = mockEntry(tileId = 20, runLength = 2, offset = 30, length = 5) val header = mockHeader(tileDataOffset = 0) val recurse: (Pmtiles.Entry) -> Sequence = { emptySequence() } val result = ReadablePmtiles.mapDirectory(entry, 0, 20, header, recurse) assertTrue(result.toList().isEmpty()) } @Test fun `tile range entry returns empty when out of bounds above`() { val entry = mockEntry(tileId = 20, runLength = 2, offset = 30, length = 5) val header = mockHeader(tileDataOffset = 0) val recurse: (Pmtiles.Entry) -> Sequence = { emptySequence() } val result = ReadablePmtiles.mapDirectory(entry, 22, 25, header, recurse) assertTrue(result.toList().isEmpty()) } } ================================================ FILE: java/mlt-cli/src/test/kotlin/org/maplibre/mlt/cli/TaskRunnerTest.kt ================================================ package org.maplibre.mlt.cli import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean class TaskRunnerTest { @Test fun `serial runner executes task immediately when thread count is less than two`() { val ran = AtomicBoolean(false) val runner = createBoundedNonRejectingTaskRunner(1) assertTrue(runner is SerialTaskRunner) runner.run(Runnable { ran.set(true) }) assertTrue(ran.get()) assertEquals(0, runner.threadCount) } @Test fun `thread pool runner reports pool size excluding the main thread`() { val runner = createBoundedNonRejectingTaskRunner(4) try { assertEquals(3, runner.threadCount) } finally { runner.shutdown() runner.awaitTermination() } } // Ensure that tasks run in parallel @Test fun `thread pool runner executes submitted tasks`() { val threadCount = 3 val runner = createBoundedNonRejectingTaskRunner(threadCount) val startLatch = CountDownLatch(1) val stopLatch = CountDownLatch(threadCount - 1) val mainThread = Thread.currentThread().threadId() try { repeat(threadCount - 1) { runner.run({ assertTrue(Thread.currentThread().threadId() != mainThread) assertDoesNotThrow { startLatch.await(1, TimeUnit.SECONDS) } stopLatch.countDown() }) } // allow threads to start startLatch.countDown() // ensure that all tasks complete assertTrue(stopLatch.await(1, TimeUnit.SECONDS)) } finally { runner.shutdown() runner.awaitTermination() } } // Add more tasks than threads to ensure that the pool doesn't reject tasks // when saturated. // Ideally we would fill the pool before allowing tasks to complete so it's // not sensitive to timing, but the main thread will execute a task at saturation // resulting in a deadlock. @Test fun `thread pool runner doesn't reject tasks`() { val threadCount = 3 val taskCount = threadCount * 10 val runner = createBoundedNonRejectingTaskRunner(threadCount) val startLatch = CountDownLatch(1) val stopLatch = CountDownLatch(taskCount) val mainThread = Thread.currentThread().threadId() try { repeat(taskCount) { runner.run({ if (Thread.currentThread().threadId() == mainThread) { // don't wait, we would deadlock } else { assertDoesNotThrow { startLatch.await(1, TimeUnit.SECONDS) } } stopLatch.countDown() }) } // allow threads to start startLatch.countDown() // ensure that all tasks complete assertTrue(stopLatch.await(1, TimeUnit.SECONDS)) } finally { runner.shutdown() runner.awaitTermination() } } } ================================================ FILE: java/mlt-cli/src/test/kotlin/org/maplibre/mlt/cli/TestUtil.kt ================================================ package org.maplibre.mlt.cli import org.junit.jupiter.api.Assertions.assertTrue fun Exception.andContains(str: String) = assertTrue(this.message?.contains(str) ?: false, "Expected exception message to contain '$str', but was '${this.message}'") ================================================ FILE: java/mlt-cli/src/test/kotlin/org/maplibre/mlt/cli/TileCoordRangeTest.kt ================================================ package org.maplibre.mlt.cli import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Test class TileCoordRangeTest { @Test fun `allows zero offset byte range`() { val range = ReadablePmtiles.TileCoordRange(42L, 3, ReadablePmtiles.ByteRange(0, 7)) assertEquals(42L, range.startTileId) assertEquals(3, range.tileCount) assertEquals(0L, range.byteRange.offset) } @Test fun `rejects negative offset byte range`() { assertThrows(IllegalArgumentException::class.java) { ReadablePmtiles.TileCoordRange(0L, 1, ReadablePmtiles.ByteRange(-1, 7)) } } @Test fun `rejects non-positive tile count`() { assertThrows(IllegalArgumentException::class.java) { ReadablePmtiles.TileCoordRange(0L, 0, ReadablePmtiles.ByteRange(0, 7)) } } } ================================================ FILE: java/mlt-core/build.gradle ================================================ plugins { alias(libs.plugins.diffplug.spotless) alias(libs.plugins.maven.publish) alias(libs.plugins.me.champeau.jmh) id 'jacoco' id 'java-library' } repositories { mavenCentral() maven { url = 'https://maven.ecc.no/releases' } } dependencies { api(libs.javaFastPFOR) implementation(libs.commons.lang3) implementation(libs.earcut4j) implementation(libs.gson) implementation(libs.guava) implementation(libs.hppc) implementation(libs.jakarta.annotation.api) implementation(libs.java.vector.tile) implementation(libs.jetbrains.annotations) implementation(libs.jts.core) implementation(libs.mapbox.vector.tile.java) implementation(libs.protobuf.java) compileOnly(libs.lombok) annotationProcessor(libs.lombok) testCompileOnly(libs.lombok) testAnnotationProcessor(libs.lombok) testImplementation(libs.jmh.core) testImplementation(libs.jmh.generator.annprocess) testImplementation(libs.junit.jupiter.api) testImplementation(libs.junit.jupiter.params) testImplementation(libs.sqlite.jdbc) testRuntimeOnly(libs.junit.jupiter.engine) testRuntimeOnly(libs.junit.jupiter.platform.launcher) } test { jvmArgs "-Dcom.google.protobuf.use_unsafe_pre22_gencode" // Forward benchmark.iterations to the test JVM so tests can read it via Integer.getInteger() systemProperty 'benchmark.iterations', System.getProperty('benchmark.iterations', '1') useJUnitPlatform { excludeTags 'benchmark' } testLogging { outputs.upToDateWhen { false } showStandardStreams = true events "passed", "skipped", "failed" exceptionFormat = "full" } finalizedBy jacocoTestReport } jacocoTestReport { reports { xml.required = true html.required = false } dependsOn test } java { // Optional feature for GeoJSON support, which adds a dependency on JTS IO Common. // This allows users to opt in to GeoJSON support without forcing the dependency on all users. registerFeature('geojson') { usingSourceSet(sourceSets.create('geojson')) dependencies { implementation(libs.jts.io.common) } } toolchain { languageVersion = JavaLanguageVersion.of(21) } } spotless { java { importOrder() target 'src/*/java/**/*.java' googleJavaFormat() removeUnusedImports() } } tasks.register('compileWrapper', Exec) { workingDir = project.projectDir doFirst { if (System.properties['os.name'].toLowerCase().contains('windows')) { executable "../resources/compile-windows.bat" } else { executable "./compile-wrapper.sh" } } } // compileJava.dependsOn compileWrapper // Disabled due to CMake compatibility issues gradle.projectsEvaluated { tasks.withType(JavaCompile).tap { configureEach { options.compilerArgs << "-Xlint:unchecked" options.compilerArgs << "-Xlint:deprecation" options.compilerArgs << "-Xlint:all" } } } tasks.withType(Test).configureEach { useJUnitPlatform() } tasks.register('validateSemver') { doLast { def version = project.version if (version == null || version == 'unspecified') { throw new GradleException("Version is not set. Please provide a version using -Pversion=") } def semverPattern = /^\d+\.\d+\.\d+$/ if (!version.matches(semverPattern)) { throw new GradleException("Version '$version' is not a valid semantic version. Expected format: major.minor.patch (e.g., 1.0.0, 2.1.3)") } println "✓ Version '$version' is valid semver" } } mavenPublishing { publishToMavenCentral(true) signAllPublications() coordinates("org.maplibre", "mlt", version) publishToMavenCentral.dependsOn validateSemver pom { name = "MapLibre Tile Specification" description = "Java implementation of the MapLibre Tile (MLT) specification" url = "https://github.com/maplibre/maplibre-tile-spec" licenses { license { name = "The Apache License, Version 2.0" url = "https://www.apache.org/licenses/LICENSE-2.0.txt" } } developers { developer { id = "maplibre" name = "MapLibre contributors" url = "https://github.com/maplibre" } } scm { connection = "scm:git:git://github.com/maplibre/maplibre-tile-spec.git" developerConnection = "scm:git:ssh://github.com:maplibre/maplibre-tile-spec.git" url = "https://github.com/maplibre/maplibre-tile-spec" } } } // Disable signing for local publishing tasks.withType(Sign) { onlyIf { !gradle.startParameter.taskNames.contains('publishMavenPublicationToMavenLocal') } } ================================================ FILE: java/mlt-core/src/jmh/java/org/maplibre/mlt/BenchmarkUtils.java ================================================ package org.maplibre.mlt; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.apache.commons.lang3.tuple.Pair; import org.maplibre.mlt.converter.ColumnMapping; import org.maplibre.mlt.converter.ColumnMappingConfig; import org.maplibre.mlt.converter.ConversionConfig; import org.maplibre.mlt.converter.FeatureTableOptimizations; import org.maplibre.mlt.converter.MltConverter; import org.maplibre.mlt.converter.encodings.EncodingUtils; import org.maplibre.mlt.converter.mvt.MvtUtils; import org.maplibre.mlt.data.MapboxVectorTile; public class BenchmarkUtils { private BenchmarkUtils() {} public static void encodeTile( int z, int x, int y, Map encodedMvtTiles, Map encodedMvtTiles2, Map compressedMvtTiles, Map encodedMltTiles, String path, String separator) throws IOException { final var encodedMvtTile = getMvtFile(z, x, y, path, separator); encodedMvtTiles.put(z, encodedMvtTile.getLeft()); encodedMvtTiles2.put(z, new ByteArrayInputStream(encodedMvtTile.getLeft())); compressedMvtTiles.put(z, EncodingUtils.gzip(encodedMvtTile.getLeft())); final var columnMapping = new ColumnMapping("name", ":", true); final var columnMappings = List.of(columnMapping); final var columnMappingMap = ColumnMappingConfig.of(Pattern.compile(".*"), columnMappings); final var isIdPresent = true; final var metadata = MltConverter.createTilesetMetadata( encodedMvtTile.getRight(), columnMappingMap, isIdPresent); final var allowIdRegeneration = true; final var allowSorting = true; final var optimization = new FeatureTableOptimizations(allowSorting, allowIdRegeneration, columnMappings); final var optimizations = TestSettings.OPTIMIZED_MVT_LAYERS.stream() .collect(Collectors.toMap(l -> l, l -> optimization)); final var config = ConversionConfig.builder() .includeIds(true) .useFastPFOR(true) .useFSST(true) .optimizations(optimizations) .build(); final var encodedMltTile = MltConverter.encode(encodedMvtTile.getRight(), metadata, config, null); encodedMltTiles.put(z, encodedMltTile); } private static Pair getMvtFile( int z, int x, int y, String path, String separator) throws IOException { var tileId = String.format("%s%s%s%s%s", z, separator, x, separator, y); var mvtFilePath = Paths.get(path, tileId + ".mvt"); var encodedTile = Files.readAllBytes(mvtFilePath); var decodedTile = MvtUtils.decodeMvt(mvtFilePath); return Pair.of(encodedTile, decodedTile); } } ================================================ FILE: java/mlt-core/src/jmh/java/org/maplibre/mlt/OmtDecoderBenchmark.java ================================================ package org.maplibre.mlt; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import org.maplibre.mlt.converter.encodings.EncodingUtils; import org.maplibre.mlt.converter.mvt.MvtUtils; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import org.springmeyer.VectorTileLayer; /** * Benchmarks for the decoding performance of OpenMapTiles schema based tiles into the MVT and MLT * in-memory representations. */ @State(Scope.Benchmark) @OutputTimeUnit(TimeUnit.MILLISECONDS) @BenchmarkMode(Mode.AverageTime) @Threads(value = 1) @Warmup(iterations = 5) @Measurement(iterations = 5) @Fork(value = 1) public class OmtDecoderBenchmark { /* java-vector-tile library */ private static final Map encodedMvtTiles = new HashMap<>(); /* mapbox-vector-tile-java library */ private static final Map encodedMvtTiles2 = new HashMap<>(); private static final Map compressedMVTiles = new HashMap<>(); private static final Map encodedMltTiles = new HashMap<>(); private static final String SEPARATOR = "_"; @Setup public void setup() throws IOException { encodeTile(2, 2, 2); encodeTile(3, 4, 5); encodeTile(4, 8, 10); encodeTile(5, 16, 21); encodeTile(6, 32, 41); encodeTile(7, 66, 84); encodeTile(8, 134, 171); encodeTile(9, 265, 341); encodeTile(10, 532, 682); encodeTile(11, 1064, 1367); encodeTile(12, 2132, 2734); encodeTile(13, 4265, 5467); encodeTile(14, 8298, 10748); } @Setup(Level.Invocation) public void resetInputStreams() { for (var is : encodedMvtTiles2.values()) { is.reset(); } } private void encodeTile(int z, int x, int y) throws IOException { BenchmarkUtils.encodeTile( z, x, y, encodedMvtTiles, encodedMvtTiles2, compressedMVTiles, encodedMltTiles, TestSettings.OMT_MVT_PATH, SEPARATOR); } @Benchmark public Map decodeMvtMapboxZ2() throws IOException { var mvTile = encodedMvtTiles.get(2); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeMvtMapboxZ3() throws IOException { var mvTile = encodedMvtTiles.get(3); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeMvtMapboxZ4() throws IOException { var mvTile = encodedMvtTiles.get(4); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeMvtMapboxZ5() throws IOException { var mvTile = encodedMvtTiles.get(5); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeMvtMapboxZ6() throws IOException { var mvTile = encodedMvtTiles.get(6); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeMvtMapboxZ7() throws IOException { var mvTile = encodedMvtTiles.get(7); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeMvtMapboxZ8() throws IOException { var mvTile = encodedMvtTiles.get(8); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeMvtMapboxZ9() throws IOException { var mvTile = encodedMvtTiles.get(9); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeMvtMapboxZ10() throws IOException { var mvTile = encodedMvtTiles.get(10); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeMvtMapboxZ11() throws IOException { var mvTile = encodedMvtTiles.get(11); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeMvtMapboxZ12() throws IOException { var mvTile = encodedMvtTiles.get(12); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeMvtMapboxZ13() throws IOException { var mvTile = encodedMvtTiles.get(13); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeMvtMapboxZ14() throws IOException { var mvTile = encodedMvtTiles.get(14); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeCompressedMvtMapboxZ2() throws IOException { var compressedMvTile = compressedMVTiles.get(2); var mvTile = EncodingUtils.unzip(compressedMvTile); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeCompressedMvtMapboxZ3() throws IOException { var compressedMvTile = compressedMVTiles.get(3); var mvTile = EncodingUtils.unzip(compressedMvTile); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeCompressedMvtMapboxZ4() throws IOException { var compressedMvTile = compressedMVTiles.get(4); var mvTile = EncodingUtils.unzip(compressedMvTile); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeCompressedMvtMapboxZ5() throws IOException { var compressedMvTile = compressedMVTiles.get(5); var mvTile = EncodingUtils.unzip(compressedMvTile); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeCompressedMvtMapboxZ6() throws IOException { var compressedMvTile = compressedMVTiles.get(6); var mvTile = EncodingUtils.unzip(compressedMvTile); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeCompressedMvtMapboxZ7() throws IOException { var compressedMvTile = compressedMVTiles.get(7); var mvTile = EncodingUtils.unzip(compressedMvTile); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeCompressedMvtMapboxZ8() throws IOException { var compressedMvTile = compressedMVTiles.get(8); var mvTile = EncodingUtils.unzip(compressedMvTile); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeCompressedMvtMapboxZ9() throws IOException { var compressedMvTile = compressedMVTiles.get(9); var mvTile = EncodingUtils.unzip(compressedMvTile); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeCompressedMvtMapboxZ10() throws IOException { var compressedMvTile = compressedMVTiles.get(10); var mvTile = EncodingUtils.unzip(compressedMvTile); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeCompressedMvtMapboxZ11() throws IOException { var compressedMvTile = compressedMVTiles.get(11); var mvTile = EncodingUtils.unzip(compressedMvTile); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeCompressedMvtMapboxZ12() throws IOException { var compressedMvTile = compressedMVTiles.get(12); var mvTile = EncodingUtils.unzip(compressedMvTile); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeCompressedMvtMapboxZ13() throws IOException { var compressedMvTile = compressedMVTiles.get(13); var mvTile = EncodingUtils.unzip(compressedMvTile); return MvtUtils.decodeMvtMapbox(mvTile); } @Benchmark public Map decodeCompressedMvtMapboxZ14() throws IOException { var compressedMvTile = compressedMVTiles.get(14); var mvTile = EncodingUtils.unzip(compressedMvTile); return MvtUtils.decodeMvtMapbox(mvTile); } } ================================================ FILE: java/mlt-core/src/jmh/java/org/maplibre/mlt/converter/encodings/fsst/FsstBenchmark.java ================================================ package org.maplibre.mlt.converter.encodings.fsst; import java.util.Base64; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; @State(Scope.Benchmark) @OutputTimeUnit(TimeUnit.SECONDS) @BenchmarkMode(Mode.Throughput) @Threads(value = 1) @Warmup(iterations = 5) @Measurement(iterations = 5) @Fork(value = 1) public class FsstBenchmark { private static final Fsst JAVA = new FsstJava(); private static final Fsst JNI = new FsstJni(); // ~30 bytes private static final byte[] SMALL = Base64.getDecoder().decode("SGV1YmFjaExpcHBlS2V0dGJhY2hTdGV2ZXI="); private static final SymbolTable SMALL_ENCODED = JAVA.encode(SMALL); // ~600 bytes private static final byte[] MEDIUM = Base64.getDecoder() .decode( "UsO8c3RlcmJlcmdFaXNlbmJlcmdLYXNzZWxlciBLdXBwZUJpbHN0ZWluVm9nZWxoZXJkTWlsc2VidXJnSG9oZSBHZWJhSGVsZHJhc3RlaW5PaG1iZXJnR3Jvw59lciBLbm9sbGVuU2Nod2VmZWxrb3BmR3Jvw59lciBCZWVyYmVyZ0FsdGVyIEJlcmcgKEhhaW5pY2gpUG9wcGVuYmVyZ0Jyb2NrZW5Sw7xja2Vyc2JpZWxHcm/Dn2VyIEthbG1iZXJnS8O8bnplbHNiZXJnSm9zZXBoc2jDtmhlV2VnZW5lcnNrb3BmUm/Dn2LDvGhsTcO2bmNoc2JlcmdXaW5ka25vbGxlbkVyYmJlcmdTdGVsbGJlcmdEb3JuYmVyZ0xvdGhyYWjDvGdlbEZlaHJlbmJlcmdBaGxzYnVyZ0hlaW5yaWNoc2jDtmhlV2V0dGVuYnVyZyAoSGFpbmxlaXRlKU3DvGhsZW5zdGVpblNjaG5lZWtvcGZBbGhlaW1lckdyb8OfZSBMYWl0ZVNvaXNiZXJnS2V1ZGVsc2t1cHBlRXNwaWdNYXVsa3VwcGVWaWt0b3JzaMO2aGVHcm/Dn2VyIEF1ZXJiZXJnSG9ja2VscmFpbkJvY2tHcm/Dn2VyIEV0dGVyc2JlcmdSaWVjaGhlaW1lciBCZXJnQnViZW5iYWRlciBTdGVpbldvbGZza29wZkJ1Y2hzY2hpcm1iZXJnU2F1aMO8Z2VsSGFuZ3N0ZWluSHV0c2JlcmdHZWllcnNow7xnZWxHZXJiZXJzdGVpblJvc3Nrb3BmV2lja2VuLUJlcmdCcmFha2JlcmdTY2htaWVkc3dpZXNlbmtvcGZCw7xobEdyb8OfZXIgRmluc3RlcmJlcmdHcm"); private static final SymbolTable MEDIUM_ENCODED = JAVA.encode(MEDIUM); // ~23kb private static final byte[] LARGE = Base64.getDecoder() .decode( "THljw6llIGfDqW7DqXJhbCBldCB0ZWNobm9sb2dpcXVlIEhlbnJpIEJlcmdzb25Mb3VpcyBCbGFuY0dhcmUgZGUgbCdFc3RGb25kYXRpb24gb3BodGFsbW9sb2dpcXVlIEFkb2xwaGUgZGUgUm90aHNjaGlsZE1hcm9jQ2hhbXBpb25uZXRDZXNhcmlhIEV2b3JhIC0gUm9zYSBQYXJrc0NvbGzDqGdlIFNvbmlhIERlbGF1bmF5Q2VudHJlIG3DqWRpY2FsSMO0cGl0YWwgZGUgam91ciBMw6lvcG9sZCBCZWxsYW5JbnN0aXR1dCBMYXNlciBWaXNpb24gZGVzIEJ1dHRlcyBDaGF1bW9udENyaW3DqWVSb3NhIFBhcmtzTWFyY2FkZXQgLSBQb2lzc29ubmllcnNQbGFjZSBIw6liZXJ0SMO0cGl0YWwgZGUgam91ciBHZW9yZ2VzIFZhY29sYVBhcmMgQ2hhcGVsbGUtQ2hhcmJvblBvcnRlIGRlIGxhIENoYXBlbGxlT3B0aWNsYWlyQmFyYsOocyAtIFJvY2hlY2hvdWFydENow6J0ZWF1IFJvdWdlQ2jDonRlYXUgTGFuZG9uTWFyeCBEb3Jtb3lMZXMgQ2FyaWF0aWRlcyBkJ0FiYmV2aWxsZUjDtHBpdGFsIEZlcm5hbmQgV2lkYWxTdGFsaW5ncmFkQ2xpbmlxdWUgVsOpdMOpcmluYWlyZSBkdSBGYXVib3VyZ0xhIEJ1dHRlUmlxdWV0U2VjcsOpdGFuIC0gQnV0dGVzIENoYXVtb250VHJpc3RhbiBUemFyYUxlcyBSb3Nlc0N1c3RpbmUgLSBSYW1leVBham9sIC0gRMOpcGFydGVtZW50VsOpbGlnb0NyaW3DqWUgLSBDdXJpYWxNYXRlbGFzIEZhY3RvcnlDb2xsw6hnZSBIZW5yaSBCZXJnc29uR2FyZSBkdSBOb3JkIChNw6l0cm8pUm9zYSBQYXJrcyAtIEN1cmlhbEphdXLDqHPDiWNvbGUgcG9seXZhbGVudGUgZGVzIFBvaXNzb25uaWVyc0NvbG9uZWwgRmFiaWVuTG9uZyBDaGFuZ1BvbnQgTWFyY2FkZXRTaW1wbG9uTWFnZW50YSAtIE1hdWJldWdlIC0gR2FyZSBkdSBOb3JkR29sZCBHU01GbGFuZHJlQ2FuYWwgU2FpbnQtTWFydGluUG9pc3Nvbm5pw6hyZUdhcmUgZGUgbOKAmUVzdEV1Z8OobmUgU3VlUGFqb2wgLSBSaXF1ZXRQYXJ2aXMgUm9zYSBQYXJrc8OJdmFuZ2lsZSAtIEF1YmVydmlsbGllcnNBZnJpY2EgaW4gYSBCb3hTcXVhcmUgZHUgMjEgQXZyaWwgMTk0NEjDtHBpdGFsIExhcmlib2lzacOocmVCb2xpdmFyVHJvcGljb3NtZXRpcXVlTWF0aHVyaW4gTW9yZWF1IC0gU2ltb24gQm9saXZhckNoYXBlbGxlIC0gQ2FpbGxpw6lEZWxpY2UgS3lveWFNb25jZWF1IEZsZXVyc0xpYnJhaXJpZSBkZXMgT3JndWVzRG91ZGVhdXZpbGxlVmVyZHVuw4lnbGlzZSBOb3RyZS1EYW1lLWRlLWwnQXNzb21wdGlvbiBkZXMgQnV0dGVzLUNoYXVtb250QXRsYXNLaW5nIENvY2tQbGFjZSBkZSBUb3JjeVBham9sTWFnZW50YUFsYmVydCBLYWhuSmF1csOocyAtIFN0YWxpbmdyYWRQbGFjZSBGcmFueiBMaXN6dExhIEZheWV0dGUgLSBQb2lzc29ubmnDqHJlTGFiYXRBTUogQmF0aW1lbnRNYXJvYyAtIEZsYW5kcmVDb25kb3JjZXRJYmlzIE9ybmFub0NvY2NpTWFya2V0Q2FtYnJhaVnFjXNvUGhpbGlwcGUgZGUgR2lyYXJkT3JkZW5lciAtIE1hcnggRG9ybW95TWFnZW50YS1HYXJlIGRlIGwnRXN0Qm91Y3J5T3B0aWNhbFJ1ZSBkZSBNZWF1eExhIENoYXBlbGxlUm9jaGVjaG91YXJ0IC0gQ2xpZ25hbmNvdXJ0TGUgUGFpbiBkZSBsYSBMaWJlcnTDqUVzcGFjZSBTcG9ydGlmIFBhaWxsZXJvbkdhcmUgZHUgTm9yZCAoUkVSKU1hcmNow6kgU2VjcsOpdGFuRHIuIEdyYXZlc0NhcnJlZm91ciBDaXR5R3JhbmdlIGF1eCBCZWxsZXMgLSBKdWxpZXR0ZSBEb2R1U2FtYnJlIGV0IE1ldXNlTUlBTVBpc2NpbmUgUGFpbGxlcm9uQ3JpbcOpZSAtIEFyY2hlcmVhdUphZCBWb3lhZ2VzUGFyYWRpc1NreU5ldENlbnRyZSBJbnRlcnByb2Zlc3Npb25uZWwgZGUgRm9ybWF0aW9uIGRlcyBDb21tZXJjZXMgZGUgbCdBbGltZW50YXRpb25OYW5kdSBNaW5pIE1hcmtldFNxdWFyZSBNb250aG9sb25QYXJpcyBHYXJlIGR1IE5vcmTDiWNvbGUgbWF0ZXJuZWxsZSBkdSBEw6lwYXJ0ZW1lbnRTYWludC1CcnVub011bGxlckxlIENhZHJlIE5vaXJMYXZlcmllIE5ldCDDoCBTZWNSdWUgUmF5bW9uZCBRdWVuZWF1IChhc2NlbnNldXIpTGEgRmF5ZXR0ZSAtIER1bmtlcnF1ZU1hZ2VudGEgLSBNYXViZXVnZVJhZGlndWV0UGxhY2UgZHUgTWFyb2NMdW4nT3JBbmRyw6lDcsOoY2hlIENvbGxlY3RpdmVNYWRvdSBTYW5ndWluQ29uZG9yY2V0IC0gVHJ1ZGFpbmVQcm9ncmVzcyBTYW50w6lQbGFjZSBkZSBsYSBDaGFwZWxsZUdyb3VwZSBTY29sYWlyZVJlc3RhdXJhbnQgQmFsYWRpw4ljb2xlIE5vcm1hbGUgU29jaWFsZUTDqXBhcnRlbWVudCAtIE1hcnggRG9ybW95TWFyY2jDqSBTZWNyw6l0YW4gKDI2KVJvbmQtcG9pbnQgZGUgbGEgQ2hhcGVsbGVRdWFpIGRlIGxhIFNlaW5lIC0gU3RhbGluZ3JhZMOJY29sZSBNYXRlcm5lbGxlTS4gS2hhbiBNb2JpbGUgRmFzaGlvblRpbnRhbWFycmVWYWxlbmNpZW5uZXNaZW4mQ29Dcmltw6llIC0gQXViZXJ2aWxsaWVyc1BhcmlzIENvbGxlZ2Ugb2YgQXJ0Uml2b2xpIFJldG91Y2hlQ29sbMOoZ2UgR8OpcmFyZCBQaGlsaXBlQ29sbMOoZ2UgR2VvcmdlcyBDbGVtZW5jZWF1VUZSIGRlIE3DqWRlY2luZSBQYXJpcyBEaWRlcm90IC0gU2l0ZSBWaWxsZW1pbsOJY29sZSBtYXRlcm5lbGxlIGRlIFRvcmN5QXV4IETDqWxpY2VzIGQnQWJyYWhhbUN1cmlhbCAtIEFyY2hlcmVhdcOJY29sZSBtYXRlcm5lbGxlIFJpY2hvbW1lTGEgTWFpc29uIE1va3JhdFBhbGFpcyBkdSBXZW56aG91Q2Fyb2xlw4ljb2xlIFN1cMOpcmlldXJlIGRlIFZlbnRlIGV0IGQnRXhwb3J0YXRpb27DiWNvbGUgcHJpbWFpcmUgQ3Vnbm90Q3LDqGNoZSBCbGV1ZUNyaW3DqWUgLSBSb3NhIFBhcmtzQ29sbMOoZ2UgcHJpdsOpIFNhaW50LVZpbmNlbnRUZW5kYW5jZUNoZXogSGFtaWRMYSBDb2lmacOocmXDiWNvbGUgcHJpbWFpcmUgQ2hhbXBpb25uZXRDdXJpYWwgLSBDcmltw6llQ29sbMOoZ2UgUm9sYW5kIERvcmdlbMOoc0NGQSBJR1NMeWPDqWUgdGVjaG5vbG9naXF1ZSBKdWxlcyBTaWVnZnJpZWRMeWPDqWUgcHJvZmVzc2lvbm5lbCBFZG1vbmQgUm9zdGFuZMOJY29sZSDDqWzDqW1lbnRhaXJlIFBoaWxpcHBlIGRlIEdpcmFyZENhaWwgLSBEZW1hcnF1YXloYWx0ZSBuYXV0aXF1ZSBkZSBsYSBWaWxsZXR0ZUNhbmFsIDEzMU1haXNvbiBaaWx2ZWxpU3VubnnDiWNvbGUgw6lsw6ltZW50YWlyZSBwcml2w6llIFNhaW50ZS1NYXJpZVBhcnRlbmFpcmUgQ3LDqGNoZVByZXNzaW5nIFVuaS1QcmVzc0x5Y8OpZSBwcm9mZXNzaW9ubmVsIHByaXbDqSBNYXJjZWwgTGFteUNhbWHDr2V1Q3LDqGNoZSBjb2xsZWN0aXZlIERlbGVzc2VydENvbGzDqGdlIEFpbcOpIEPDqXNhaXJlw4ljb2xlIHByaW1haXJlIE1hdGhpc1N0ZWxsaWHDiWNvbGUgw6lsw6ltZW50YWlyZSBkZSBsYSBHdWFkZWxvdXBlR2FyZSBkdSBOb3JkTGEgY8WTdXIgZGVzIHBhaW5zQ29sbMOoZ2UgR2VvcmdlcyBNw6lsacOoc0NvbGzDqGdlIExhbWFydGluZURyYWdvbiBXb2tTcXVhcmUgU2ltcGxvbi1BbWlyYXV4w4ljb2xlIHByaW1haXJlIENsaW5nbmFuY291cnRTYWxsZSBkZSBtdXNjdWxhdGlvbiBQYWlsbGVyb27DiXRhYmxpc3NlbWVudCBzdXDDqXJpZXVyIHByaXbDqSBQcm9ncmVzcyBTYW50w6lTZWNvdXJzIHBvcHVsYWlyZSAtIEbDqWTDqXJhdGlvbiBkZSBQYXJpc0x5Y8OpZSBHw6luw6lyYWwgTGFtYXJ0aW5lw4ljb2xlIE1hdGVybmVsbGUgQm95LVplbGluc2tpw4ljb2xlIMOpbMOpbWVudGFpcmUgZGUgVG9yY3lNYWdlbnRhIC0gTWF1YmV1Z2Ug4oCTIEdhcmUgZHUgTm9yZEJvIEJ1blN1cCBjYXJlZXJMYSBQaWNjb2xhIFNpY2lsaWFJTlNFRUMgU2Nob29sIG9mIEJ1c2luZXNzICYgRWNvbm9taWNzQ0ZBIFN0ZXBoZW5zb27DiWNvbGUgUG9seXZhbGVudGUgUGFqb2xKYXJkaW4gZHUgMTIyIHJ1ZSBkZXMgUG9pc3Nvbm5pZXJzU0hSb2Nyb3kgU2FpbnQtVmluY2VudC1kZS1QYXVsUmVsYWlzIExhIFBvc3RlQ2Fpc3NlIG5hdGlvbmFsZSBkJ2Fzc3VyYW5jZSB2aWVpbGxlc3NlTGEgQ3Jpw6llUGF0aW5vaXJlIFBhaWxsZXJvbkNlbnRyZSBJbnRlciBFbnRyZXByaXNlIGRlIEZvcm1hdGlvbiBlbiBBbHRlcm5hbmNlw4ljb2xlIHBvbHl2YWxlbnRlIGRlIGxhIEdvdXR0ZSBkJ09ySmFyZGlucyBSb3NhIEx1eGVtYnVyZ0Nvb2snbiBXaXRoIENsYXNzIFBhcmlzw4ljb2xlIG1hdGVybmVsbGUgVGNoYcOva292c2tpTWF0aGlzQ29sbMOoZ2UgcHJpdsOpIEx1Y2llbiBkZSBIaXJzY2hNYWlzb24gZGVzIEFzc29jaWF0aW9uc1JvdG9uZGUgZGUgbGEgVmlsbGV0dGVTdXAgZGUgcHViQ3LDqGNoZSBjb2xsZWN0aXZlR3Jlc3NldMOJY29sZSBtYXRlcm5lbGxlIFNpbW9uIEJvbGl2YXJDb3VycyBGbG9yZW50w4ljb2xlIFNlY29uZCBEZWdyw6kgUHJvZi4gUHJpdsOpZSBTYWludC1Mb3VpcyBVbmlvbiBBY2Fkw6ltaWVWaWxsYSBkZSBDaGluZUx5Y8OpZSBwcm9mZXNzaW9ubmVsIEd1c3RhdmUgRmVycmnDqXBpenphTGVzIEphcmRpbnMgZCfDiW9sZcOJY29sZSDDqWzDqW1lbnRhaXJlIENhdsOpUGljYXJkQ29sbMOoZ2UgRGFuaWVsIE1heWVyw4ljb2xlIHBvbHl2YWxlbnRlIFNpbXBsb25HaWJlcnQgSm9zZXBoIC0gUGFyaXMgWFZJSUnDiWNvbGUgbWF0ZXJuZWxsZSBHb3V0dGUgZCdPck5KRGlhbW9uZCBIYWlyQ29sbMOoZ2UgQ2hhcmxlcyBQw6lndXlIYXV0LVBhcmxldXJzIFN5c3TDqG1lc8OJY29sZSBtYXRlcm5lbGxlIEFtaXJhdXhCVFMgSGVucmkgQmVyZ3NvbkNoYXVkcm9uIC0gU2FpbnQtTWFydGluTGUgY2FkcmUgbm9pcsOJY29sZSDDqWzDqW1lbnRhaXJlIE1hdXJpY2UgR2VuZXZvaXjDiWNvbGUgw6lsw6ltZW50YWlyZSBkJ09yYW5TcXVhcmUgRnJhbsOnb2lzZS1Iw6lsw6huZSBKb3VyZGHDiWNvbGUgbWF0ZXJuZWxsZSDDiWNsdXNlcyBTYWludC1NYXJ0aW5DcsOoY2hlIGNvbGxlY3RpdmUgbXVuaWNpcGFsZSBkZSBSb3VlbkxlIHBpZWQgw6AgdGVycmVIb2xpZGF5IElubiBFeHByZXNzIDogSMO0dGVsIFBhcmlzLUNhbmFsIERlIExhIFZpbGxldHRlTGEgRmF5ZXR0ZSAtIE1hZ2VudGFDYW1wdXMgbGFuZ3Vlc8OJY29sZSBldCBDb2xsw6hnZSBTYWludC1HZW9yZ2VzIChwcml2w6kpTHljw6llIHByb2Zlc3Npb25uZWwgU3V6YW5uZSBWYWxhZG9uSUNEIEludGVybmF0aW9uYWwgQnVzaW5lc3MgU2Nob29sw4ljb2xlIMOJbMOpbWVudGFpcmUgRG91ZGVhdXZpbGxlTGEgTWFybW90acOocmUgUMOpdHJlbGxlTHljw6llIGfDqW7DqXJhbCBldCB0ZWNobm9sb2dpcXVlIHByaXbDqSBDaGFybGVzIGRlIEZvdWNhdWxkUGxhbmV0IENoYW50aWVyUG9saWNlIE5hdGlvbmFsZcOJY29sZSDDqWzDqW1lbnRhaXJlIENsYXVkZSBWZWxsZWZhdXhDb2xsw6hnZSBMYSBHcmFuZ2UgYXV4IEJlbGxlc1NxdWFyZSBBcmlzdGlkZSBDYXZhaWxsw6ktQ29sbDEwOCBDYWbDqUxlIE1vdWxpbiBkZSBsYSBQYWxldHRlw4ljb2xlIHByaW1haXJlIGQnYXBwbGljYXRpb25CaWJsaW90aMOocXVlIFbDoWNsYXYgSGF2ZWxJbnN0aXR1dCBkZSBHZXN0aW9uIFNvY2lhbGUgLSDDiWNvbGUgZGVzIFJlc3NvdXJjZXMgSHVtYWluZXNMaWJyYWlyaWUgTm9yZGVzdMOJY29sZSDDqWzDqW1lbnRhaXJlIGRlIGwnw4l2YW5naWxlQ2VudHJlIEludGVyLUVudHJlcHJpc2VzIGRlIEZvcm1hdGlvbiBlbiBBbHRlcm5hbmNlw4ljb2xlIMOpbMOpbWVudGFpcmUgT3VyY3FMYSBEaW1lbnNpb24gRmFudGFzdGlxdWVMYSBSZW5haXNzYW5jZUNvdWRlcmPDiWNvbGUgw4lsw6ltZW50YWlyZSBTaW1vbiBCb2xpdmFyTm8gQ29tbWVudFBhcmlzIENow6J0ZWF1IFJvdWdlw4ljb2xlIG1hdGVybmVsbGUgU2FpbnQtTHVjw4ljb2xlIHByaW1haXJlIEHDiWNvbGUgTWF0ZXJuZWxsZSBBcmNoZXJlYXVHb29kIElkZWFzIGZvciBFdmVyeWRheSBMaWZlTGlicmFpcmllIEZsYW1iZXJnZUZyYW5wcml4U3F1YXJlIE1hcmMtU2VndWluQWxob3VkYcOJcGljZXJpZSBmbGV1cmllw4ljb2xlIMOpbMOpbWVudGFpcmUgRXVnw6huZSBWYXJsaW7DiWNvbGUgcHJpbWFpcmUgZCdhcHBsaWNhdGlvbiBCTHljw6llIHByb2Zlc3Npb25uZWwgQXJtYW5kIENhcnJlbMOJY29sZSBwcmltYWlyZcOJY29sZSDDqWzDqW1lbnRhaXJlIEN1cmlhbENvbGzDqGdlIE1hcmllIEN1cmllw4ljb2xlIG1hdGVybmVsbGUgTG91aXMgQmxhbmNEb3VibGUgQm9uaGV1csOJY29sZSBtYXRlcm5lbGxlIE1hcmNhZGV0w4ljb2xlIG1hdGVybmVsbGUgTWFyeCBEb3Jtb3nDiWNvbGUgw6lsw6ltZW50YWlyZSBkJ0F1YmVydmlsbGllcnNDZW50cmUgZOKAmUFuaW1hdGlvbiBMYSBHcmFuZ2UgYXV4IEJlbGxlc8OJY29sZSDDqWzDqW1lbnRhaXJlTGEgTG91dmVLVENTcXVhcmUgUmF5bW9uZC1RdWVuZWF1UGFyYWRpcyBkZSBTdXNoaVBhcmlzIExhIEdvdXR0ZS1kJ09yQW50ZW5uZSBKZXVuZXMgTW9udC1DZW5pc8OJY29sZSBwcml2w6llIFNhaW50LUxhdXJlbnRMYSBGYXlldHRlIC0gTWFnZW50YSAtIEdhcmUgZHUgTm9yZENvbnNlcnZhdG9pcmUgbXVuaWNpcGFsIGR1IFhJWGUgSmFjcXVlcyBJYmVydExlIENlbnRxdWF0cmUgLSAxMDRMYSBMaWJyZXJpYUNhcmR5w4ljb2xlIG1hdGVybmVsbGUgZXQgcHJpbWFpcmUgU2FpbnQtR2VvcmdlcyAocHJpdsOpKUxpYnJhaXJpZSBGaWR1Y2lhaXJlSGFsdGUtZ2FyZGVyaWVBbnRlbm5lIEpldW5lIEZsYW5kcmVzTcOpbcOpIGRhbnMgbGVzIE9ydGllc1NxdWFyZSBTaW1wbG9uTHljw6llIHByb2Zlc3Npb25uZWwgSGVjdG9yIEd1aW1hcmRHYXJlIGR1IE5vcmQgTm9jdGlsaWVuU3F1YXJlIFBhdWwgUm9iaW5TcXVhcmUgTWFkZWxlaW5lIFRyaWJvbGF0aUtpbmcgUGFyaXMgTW9iaWxlRm9vZCBIb3VzZUZyYW5rb2RlY2jDiWNvbGUgw6lsw6ltZW50YWlyZSBKZWFuLUZyYW7Dp29pcyBMw6lwaW5lUGl6emVyaWEgVG9kYSBMJ0FrZWxGcmFuc2kgU2hvcEJhbmFkaXIgQ2VudGVyIFBhcmlzQ29sbMOoZ2UgRWRtb25kIE1pY2hlbGV0QnVpIEJlYXV0w6nDiWNvbGUgcHJpbWFpcmUgSGVucmkgTm9ndcOocmVzTG9uZ3RlbXBzLi4uQ29pZmZ1cmUgU3R5bGVQYXJpcyBNb250aG9sb25DcsOoY2hlIGNvbGxlY3RpdmUgUGhpbGlwcGUgZGUgR2lyYXJkUHVibGlvdGjDqHF1ZUFsaXphIEFmcm9TcXVhcmUgZGUgQ2xpZ25hbmNvdXJ0U3F1YXJlIEV1Z8OobmUgVmFybGluSmFyZGluIFJhY2htYW5pbm92R29sZGVuIEZvb2TDiWNvbGUgw6lsw6ltZW50YWlyZSBSaWNob21tZUFsbG8gUmFwaWRvIFBpenphWXVwaGEgQmVhdXTDqSA5WmVicmEgU2hvZXNDaW1ldGnDqHJlIGRlcyBKdWlmcyBwb3J0dWdhaXNDcsOoY2hlIG11bmljaXBhbGUgQ2hhdW1vbnQgTGVwYWdlQ291cmlyQmlibGlvdGjDqHF1ZSBGcmFuw6dvaXMgVmlsbG9uU3F1YXJlIGRlIGxhIE1hZG9uZUxpYnJhaXJpZS1wcmVzc2VSYXdhc2kgdMOpbMOpY29tQ29sbMOoZ2UgVmFsbXlKYXJkaW4gQW1hZG91IEhhbXDDonTDqSBCw6LDiWNvbGUgbWF0ZXJuZWxsZSBQaWVycmUgQnVkaW7DiWNvbGUgw6lsw6ltZW50YWlyZSBBcXVlZHVjU3F1YXJlIEhlbnJpIENocmlzdGluw6lTaG9wSmFyZGluIEx1Yy1Ib2ZmbWFublNxdWFyZSBkZSBsJ8OJdmFuZ2lsZUNlbnRyZSBQYXJpcyBBbmltJyBDdXJpYWxQYXJpcyBCb2lub2RLaWxvdXRvdcOJY29sZSDDqWzDqW1lbnRhaXJlIEFybWFuZCBDYXJyZWxBIE9uZSBTaG9wTWF0b3PDiWNvbGUgcG9seXZhbGVudGUgw4ltaWxlIER1cGxvecOpR2FyZSBkdSBOb3JkIC0gRHVua2VycXVlTGUgUGFuYW1lQmFsYWRlcyBTb25vcmVzIFJlY29yZHPDiWNvbGUgw6lsw6ltZW50YWlyZSBQaWVycmUgQnVkaW5TcXVhcmUgZHUgUXVhaSBkZSBsYSBTZWluZVNxdWFyZSBDdXJpYWxMaWJyYWlyaWUgZHUgQ2FuYWxMYSBCYW5kZSBkZXMgQ2luw6lzTHljw6llIGfDqW7DqXJhbCBDb2xiZXJ0QWZyb2NsYXNzVGhpc2hhbiBTdXBlciBNYXJjaMOpRmx1eCBkJ2VuY3JlLiBDb21PJ1RhY29zUmFkaW8gTHlyZWxNb25vcCdMYSBSdWNoZSAtIMOJY29sZSBwcml2w6llIGRlcyBtw6l0aWVycyBkZSBsYSBjcsOpYXRpb25QYXBldGVyaWUgUGVyamFjVmVydCBkJ09KYXJkaW4gZGUgbCfDrmxvdCBSaXF1ZXRIdWJpesOJY29sZSDDqWzDqW1lbnRhaXJlIExvdWlzIEJsYW5jw4ljb2xlIFByaXbDqWUgU2FpbnQtQmVybmFyZCBkZSBsYSBDaGFwZWxsZUdsb2J1cyBTdGFyQWZybyBSdWJ5IENvc23DqXRpcXVlc1N1bmJ1cnlNYXJ0ZWwgQnJpY29sYWdlUGFyaXMgR2FyZSBkZSBsJ0VzdEJpYmxpb3Row6hxdWUgTWF1cmljZSBHZW5ldm9peFBob3RvIE9saXZlU2Fkw6lzaG9lc0dhcmUgZHUgTm9yZCAoZ2FyZSByb3V0acOocmUpR2FiIExpbmVMZSBSaWRlYXUgUm91Z2VPJ01vS2HDiWNvbGUgZHUgU29sZWlsQ29sbMOoZ2UgTWFyeCBEb3Jtb3lTcXVhcmUgTWFyY2VsIE1vdWxvdWRqacOJY29sZSDDqWzDqW1lbnRhaXJlIExhZmF5ZXR0ZVBhcmlzIE1ldWJsZXPDiXRvaWxl4oCLIExhZmF5ZXR0ZU9mZmljZSBEZXBvdFRTIFRlbGVjb21TQVJMIExlIERha2Fyb2lzR3JvdW5kIFplcm8sIE5hdGlvbmFsZSA3IFJlY29yZHNPcHRpYydBbGwgQXZlbnVlUG9saWNlIGRlcyBUcmFuc3BvcnRzQi5ELiBpbmZvcm1hdGlxdWVWSSBab25lRW1tYcO8c0NhbGkgTmFpbHNBdSBHcmVuaWVyIGR1IENoYW1wQ2FycmVmb3VyIEJpb0JpYmxpb3Row6hxdWUgQ2xhdWRlIEzDqXZpLVN0cmF1c3NMZSBCYXIgQ29tbXVuTGEgRmF5ZXR0ZSAtIFNhaW50LVF1ZW50aW4gLSBHYXJlIGR1IE5vcmRTcXVhcmUgU2FpbnQtQmVybmFyZCAtIFNhw69kIEJvdXppcmlMeWPDqWUgZ8OpbsOpcmFsIGV0IHRlY2hub2xvZ2lxdWUgamFjcXVhcmTDiWNvbGUgbWF0ZXJuZWxsZSBBcXVlZHVjw4ljb2xlIMOJbMOpbWVudGFpcmUgUHJpdsOpZSBMdWNpZW4gZGUgSGlyc2NoTsOpZ2F0aWYgKyBTZXJ2aWNlIGFyZ2VudGlxdWVBbWlyYWwgU3RvcmVSZWxheUNvdXJzIGRlcyBoYWxsZXPDiWNvbGUgbWF0ZXJuZWxsZSBldCDDqWzDqW1lbnRhaXJlIExvdWlzLUJsYW5jTU0gQWxpbWVudGF0aW9uTFNUQXV4IFJlbmRlei1Wb3VzIGRlcyBBbWlzTWFkZWVoYSAtIEJlYXV0w6kgZCdBZnJpcXVlS2lvc3F1ZSBkZSBQYXJpc0xlIE1lcmxlIE1vcXVldXJTcXVhcmUgTMOpb27DiWNvbGUgSW50ZXJuYXRpb25hbGUgQWxnw6lyaWVubmVTUksgRXhvdGlxdWVBIG9uZSBwcmludGVyc01haXNvbiBkJ0Vubm91ck1haXNvbiBkZSBsYSB2aWUgYXNzb2NpYXRpdmUgZXQgY2l0b3llbm5lIGR1IDEwZSBhcnJvbmRpc3NlbWVudFJNIGFsaW1lbnRhdGlvbiBnw6luw6lyYWxlRXhvIFN5bXBhTGUgTG9jYWxTdXBlcm1hcmNow6kgQXNpZSAtIEV4b0xpbGlhbmUgZGUgTWF0b3NCb2lzIERvcm1veUFDIG1hdGnDqHJlQmFyLVRhYmFjIExlIEJlcmdlcmFjRWRlbiBwaG9uZURpc2NvdW50IE1vYmlsZVNxdWFyZSBBbGFpbiBCYXNodW5nTHljw6llIEFsYmVydCBKYWNxdWFyZFNlcnZpY2UgZCdJbnZlc3RpZ2F0aW9uIFRyYW5zdmVyc2FsZVVuaXZlcnMgTGluZVl2ZXMgUm9jaGVyIFBhcmlzIEZsYW5kcmVUaGF2ZSBFeG9CLXZyYWNPcHRpYyBFeHBlcnTDiWNvbGUgw6lsw6ltZW50YWlyZSBDaGFicm9sQ29uc2VpbCBkZSBwcnVkJ2hvbW1lcyBkZSBQYXJpc1BvaW50IFNBdGVsaWVyIEZsb3JhbEdhbWJheE1haXNvbiBEb2R1IERpbmRvblBhcmlzIE9yZ3VlcyBkZSBGbGFuZHJlU04gU3ViYXRlbEVzcGFjZSBDaMOidGVhdSBMYW5kb25MaWJyYWlyaWUgQmF6YXLDiWNvbGUgRWxlbWVudGFpcmUgU2ltb24gQm9saXZhclRyYW5zZmx1eENhcnLDqSBWb3lhZ2VNYXJpb25uYXVkQW1hemluZyBCZWF1dHlDcsOoY2hlTWFtYSBDYWtlRmxldXJzIGRlIEZyYW5jZU9yaWVudGFsIERpc2NvdW50TGUgUGV0aXQgTWFyY2jDqUZhY2UgYSBGYWNlRnJhbmNlIENvbW9yZXMgVm95YWdlc8OJY29sZSBtYXRlcm5lbGxlIENhcnJlbEUuIExlY2xlcmMgUmVsYWlzQkFUVCBDT09QRWxlY3RybyBBZmZhaXJlc01hcmNow6kgRnJhbnByaXhFY29kYWlyIFBhcmlzQmlibGlvdGjDqHF1ZSBIZXJnw6lUaWVuZGEgRXNxdWlwdWxhc0lHQiBUw6lsw6ljb21FbW1hw7xzIGJvdXRpcXVlIHNvbGlkYWlyZUJsYWNrQm94ZUNvbGzDqGdlIEJlcm5hcmQgUGFsaXNzeVN1cGVyIE1hcmtldExlIEJsYW5jb0xhIFLDqWd1bGnDqHJlTGUgS2lvc3F1ZSBkZSBQYXJpc01hw69kYSBCZWF1dHlMJ8OJcGljZXJpZSBkdSAxMDREYW55IENhc2jDiWNvbGUgbWF0ZXJuZWxsZSBMw6lvbiBTY2h3YXJ0emVuYmVyZ1JlcHJvZ3JhcGhpZSBMZXMgSW1hZ2VzIE51bcOpcmlxdWVzw4ljb2xlIG1hdGVybmVsbGUgU2FkaSBMZWNvaW50ZUxpc3NhYyBPcHRpY2llbkF0ZWxpZXIgU29saUN5Y2xlUHJpbmNlIEpvcmdlR29sZE1pY3JvIGNyw6hjaGUgUGlyYXRlcyBkJ2VhdSBkb3VjZUwnSW52aXQnIMOgLWxpcmVTdXBlcmV0dGVMaWJyYWlyaWUgSXNsYW1pcXVlTHljw6llIHBvbHl2YWxlbnQgbCdJbml0aWF0aXZlTGlicmFpcmllIE11c2ljYWxlU2VydmljZXMgJiBUaWNrZXRzIFJBVFAgKEJhcmLDqHMgLSBSb2NoZWNob3VhcnQpR3VpY2hldCBUcmFuc2lsaWVuUnVlIGRlcyBQb2lzc29ubmllcnNDRiBpbmZvcm1hdGlxdWVDYXJyZWZvdXIgQ2l0eSBQYXJpcyBSaXF1ZXTDiWNvbGUgw4lsw6ltZW50YWlyZSBCZWx6dW5jZVBhcGV0ZXJpZSBNZXJjZXJpZSBKb3VybmF1eFBhcmlzIEluZm9ybWF0aXF1ZUJpam91IEpvY2VseW5lQmlvY29vcCBDYW5hbCBCaW9Fc3BhY2UgSmV1bmVzIEdvdXR0ZSBEJ09yTW9uIGNvbmNlcHQgaGFiaXRhdGlvbkxlcyBNYWxpY2lldXggZGUgTGFmYXlldHRlQ29sY2hpZGVGcmFuY2UgNEcgVMOpbMOpY29tS2lvc3F1ZSBKZXVuZXMgLSBMYSBHb3V0dGUgZCdPckhhdmFzIFZveWFnZXNBbWVyaWNhbiBDb3Ntw6l0aXF1ZXNNYXJjaMOpIENyaW3DqWUtQ3VyaWFsVmluZ3Rla2hvbmdMaXRvdGUgZW4gVMOqdGVDaXTDqSBTY29sYWlyZSBIZW5yaSBCZXJnc29uIC0gSmFjcXVhcmRBbGltZW50YXRpb24gR8OpbsOpcmFsZUNhcnJlZm91ciBNYXJrZXQgUGFyaXMgT3JuYW5vWmFyYVNPUyBNb2JpbGUgUG9pbnRMaWJyYWlyaWVPcmNoaWTDqWUgVGhhaSBCZWF1dMOpQ3VsdHVyZSBldCBMaWJlcnTDqU1haXNvbiBkZXMgQXNzb2NpYXRpb25zIGR1IFhJWGVGcnVpdHMgZXQgTMOpZ3VtZXNNYXJjaMOpIE9ybmFub0dhbGVyaWVzIGR1IE1vYmlsaWVyU29sZWlsTGVzIFjDqXJvZ3JhcGhlcyDDqWRpdGlvbnNEZXMgZmxldXJzIGNoZXogc29pTGUgQmlzdHJvdCBkdSBOb3JkTGUgQm9uIENvaW5MZSBMdXTDqXRpYUNvbGzDqGdlIEJvc3N1ZXQgLSBOb3RyZS1EYW1lQ29tbWlzc2FyaWF0IENlbnRyYWwgZGUgUG9saWNlIGR1IDEwZU1vbm9wcml4RG91YmxlIEV4ZW1wbGFpcmVJbnRlcm1hcmNow6kgRXhwcmVzc0FmcmkgUGhvbmVTcXVhcmUgQWxiYW4gU2F0cmFnbmVTcXVhcmUgZGUgSmVzc2FpbnRTdXDDqXJldHRlIGRlIEJvdWNyeUJvdXRpcXVlIGZvciBUb21vcnJvd0NoZXogRm91Y2hlciBNw6hyZSAmIEZpbGxlQWRvbmlzIEZsZXVyc0xpZGxQYXJpcyBQaGlsaXBwZSBkZSBHaXJhcmRTdXBlcmV0dGUgTWFya2V0Q29tbWlzc2FyaWF0IGRlIGxhIEdvdXR0ZSBkJ09yU3RvcmUgTUsyTWFyaW4gZCdFYXUgRG91Y2VEaWdpdGFsIEluZm9ybWF0aXF1ZUxlYWRlciBQcmljZUJhemFyIEwnT2xpdmVGcsOocmVzLVBob25lcy5uZXRKYXJkaW4gaW50w6lyaWV1ciBTYWludC1MYXphcmVQYXJpcyBMb3VpcyBCbGFuY01hcnJha2VjaFNxdWFyZSBMb3Vpc2UgZGUgTWFyaWxsYWNEw6lwaWwgU29mdE9wdGlxdWUgVG9yY3lMaWJyYWlyaWUgVmlnbmV0dGVzTWFpc29uIEFncmViaVBhcGV0ZXJpZXMgU2lsbEdsb2JhbCBQTSBFeHByZXNzRiZBIFRlbGVjb21KYWhhbiBtaW5pIG1hcmtldFVOSSBzdXJnZWzDqUxlIGZvdXJuaWwgZCdBbmRyw6l6aWV1eEx5Y2Ftb2JpbGVQYXJpcyBNYXJ4IERvcm1veUxpYnJlIFNlcnZpY2UgZGUgbGEgR2FyZVNBUkwgU3JpIEV4b3RpcXVlSmFyZGluIGRlIGxhIENvdXIgZGUgbGEgRmVybWUgU2FpbnQtTGF6YXJlU1lLIEFmcm9Qw6lww6kgU2FudGFuYUxlcyBOb3V2ZWF1eCBSb2JpbnNvbkJvZHkgTWludXRlTGVzIEJ1dmV1cnMgZCdFbmNyZU15IEF1Y2hhbkJpYmxpb3Row6hxdWUgR291dHRlIGTigJlPckJDSU0gaW5mb3JtYXRpcXVlSmFyZGluIE1hcmllbGxlLUZyYW5jb2lDZWxsU2FpbnQtTWFjbG91QXV0cmUgVnVlUGFyaXMgSmF1csOoc0ltcHJpbWVyaWUgUG9seXByaW1GbmFjTWNEb25hbGQnc0xhIFTDqnRlIGRhbnMgbGUgRnJvbWFnZVJhYXNpIFRlbGVjb21QYXJpcyBUcmF2ZWxTb2Npw6l0w6kgR8OpbsOpcmFsZUNyb2l4LVJvdWdlTGFtYXp1bmFTaGF5YW4gQ3LDqnBlUm9zZSBSb3VnZSBSZWxheGF0aW9uT3B0aWMgMjAwME5vY8OpYUFsbMO0IFNhcmluYUxlYmFyYUxhdGlubyBQaXp6YU3DqWRpYXRow6hxdWUgRnJhbsOnb2lzZS1TYWdhbktpc3NvIFN1biBDYXNoIGFuZCBDYXJyeUVzcGFjZSBGbGV1cnNIdW16YSBCZWF1dMOpUml2b2xpIFRlbGVjb21CaWpvdXRlcmllIFNhaW50LU1hcnRpbk5hdHVyYWxpYSBNYXJ4IERvcm1veUJpam91dGVyaWUgSG9ybG9nZXJpZVBvaXNzb25uaWVycyAtIE5vcmRBbmV4b051bWJlciBPbmUgLSBQcm9kdWl0cyBBZnJpY2FpbnNBdGVsaWVyIExlamV1c25lS2lmYWtQcmVzdGlzc2ltb1NlYmJhaCBwcmltZXVyc1RydWRhaW5lIE9wdGlxdWVNYWdhc2luIGRlIGNoYXVzc3VyZXNQcmluY3kgQmVhdXR5IFBhbGFjZUZyZW5jaCBDcsOqcGVzU2VjcsOpdGFuIE3DqW5hZ2VyTW9oYW4gSmV3ZWxsZXJ5IE1hcnRDb3B5LVRvcFBvaXNzb25uaWVycyAtIENoYW1waW9ubmV0VmVybmF6b2JyZXMgR3JlY29QZXJjaW5nIFN0YWxpbmdyYWRQYXJpcyBTdGFyIEZvb2RBcmMgZW4gQ2llbEd1ZXJyaXNvbEtveWFrYSBNYXJrZXRLYXZlcnkgQ2FzaCAmIENhcnJ5RMOpcMO0dCBkZSBMYSBDaGFwZWxsZVlhdG9vIFBhcnRvb0xlcyDDqXRoaXF1ZXTDqXNTdXBlciBtYXJrZXRFeG8gTWFya2V0QWRhbmFQYXJpcyBTY29vdGVyaXNPcHRpY0NNIEV4b3RpcXVlQ2hpY2tlbiBTcG90Q2FjdHVzICYgTWFpc29uLUNhZG9FdmEgVHJhdmVsc0FsaW1lbnRhdGlvbiBnw6luw6lyYWxlU0FSTCBTYW1pYU1hemFsYXkgQ291dHVyZUxlcyBkw6lsaWNlcyBkZSBsYSBDaGFwZWxsZU9wdGknbWlzdGVFbHNhICYgSnVzdGluU1RSIEFsaW1lbnRhdGlvbiBHw6luw6lyYWxlS2FuZ2Z1IE1hc3NhZ2VSb3lhbCBLZWJhYkxhIEJhbHVzdHJhZGVPcHRpcXVlTGEgUHJvc3DDqXJpdMOpQ2hhdXNzZSBDb25mb3J0TGEgcGF1c2UgdHJhaXRldXJEdWMgQ2hyaXN0b3BoZUxlIENpZWwgZXN0IMOgIHRvdXQgbGUgTW9uZGVDYXByaWNlIFN0eWxlUm9uZC1Qb2ludCBkZSBsYSBDaGFwZWxsZVNlbGVjdG91clNleHkgU2hvcFNoYXRoYSBUaW1lRm9vZExhIEdyYXBwZSBkJ09yQXV4IFF1YXRyZSBTYWlzb25zTGVzIEFsaXplcyBPcHRpY2llbnNBbHRvJ1RhY29zQ2hleiBUb255R2lmaVZpamF5U3Vid2F5TGUgcGV0aXQgbWFyY2jDqSBkZSBSaXF1ZXRNYXJjaMOpIGRlIExhIENoYXBlbGxlUGFyaXMgQm91dGlxdWUgR2FyZSBkZSBsJ0VzdEF1IHBhcyBkZSBTYWludC1Mb3Vpc0NoZXogR2FzdG9uS2luZ01GIDIwMDBTQVJMIENpc3NlICYgRmlsc1JEQyBCaW8gTmEgQmlvTGUgQsOpbmluUGFuZGEgRmFzdCBGb29kUGhvbmUgQm91dGlxdWVDcmVwJ1dheVBhcmlzIENhc2ggYW5kIENhcnJ5TGEgUGxhdGVmb3JtZSBkdSBCw6J0aW1lbnQgY29tcGFjdEQ0IENvc23DqXRpcXVlQm9keScgTWludXRlUnVlIE9yZGVuZXJab25lIE1vYmlsZUJpbyBHw6luw6lyYXRpb25MJ2FwcGFydGVtZW50IEVtbWHDvHNIRkMzOCBnb3VybWV0U28nRmFzdFBoYXJtYWNpZSBkZSBsYSBDaGFwZWxsZVByb3h5SW5mb3JtYXRpcXVlIE1vYmlsZVNhYmFiYVdheCBKb2xpIEFmcmlxdWVHb29kYXlBZGVzIG1vdG9zVGVsZS1Qb3AtTXVzaWtHdWluw6llIFN0b3JlTGUgUMOpbGljYW5QYXJpcyBTdG9yZUxlIEZsYXNoQnJpY29sZXhDYWlzc2UgZCfDiXBhcmduZSBQYXJpcyBMYSBDaGFwZWxsZURpZGltIEtlYmFiUGFyaXMgQ2FuYXDDqXNDcmVwZW1lbidCb25PbGl2ZXMsIMOpcGljZXMsIGZydWl0cyBzZWNzLCBldGMuLi5KREMgQWZyaWNhIC0gTGUgR3JlbmllciBkJ0FmcmlxdWVSb3NhIEluc3RpdHV0TGEgVmllIENsYWlyZUFlbGlhIER1dHkgRnJlZUxlIFByb3ZlbmNpYWxBQkkgRXhvdGlxdWVOaW5hIE1ldWJsZXMgJiBEZWNvw4lsw6lnYW5jZSAmIFZvdXNSaXF1ZXQgRmFzaGlvbkxlIFN1cGVyY29pbk1pc3Npw6BCZWF1dMOpcyBkdSBNb25kZUJhZ2VsIENvcm5lckF1IFLDqWd1bGF0ZXVyS2hhbiBUZWxlY29tbXVuaWNhdGlvbk1vbiBQZXRpdCBJbnN0aXR1dEVzdCdhaXIgVm95YWdlc1N1biBNb29uIFRlbGVjb21Nb24nRHdpY2gnQWZnaGFuIE1hcmtldEhvJ3Mga2ZlTGEgTWVkaW5hTMOpb25waG90RGhha2EgSW5mb3JtYXRpcXVlUm9jayB0aGUgS2lsaW1TYWJiYWggT3JpZW50YWxlZm9yIE1hYyAmIFBDUGFkbWEgdGVsZWNvbUxhbmRvbiBEw6lsaWNlc0xlIENvc3RhIFJpY2FTYXZldXIgMTlQb2ludCBTb2xlaWxTb3VuZGFyIFRyYXZlbHNMYSBSb3NlIGRlIFBhcmlzRyZNIENoYXVzc3VyZXNPdWVkLVJoaW91QnJpY29yb3V4WcO8biBQcmltZXVyIMOpcGljZXJpZSBmaW5lWGluIEthaSBZdWVWRUdBIFNrYXRlc2hvcEV1cm9wIFBob25lUGl0YSBCdXNpbmVzcyBDZW50ZXJGcm91LUZyb3UgTWVyY2VyaWUgQ29udGVtcG9yYWluZcOJcGljZXJpZSBkdSBDb2luIC0gQWxpbWVudGF0aW9uIEfDqW7DqXJhbGVJbGFuaW8gTW9iaWxlQkggc2Nvb3RlckxlIEZvbnRlbm95TCdBZHJpYXRpcXVlUGFqb2wgLSBQbGFjZSBIZXJiZXJ0UG9tbScgcmVzdG9DZW50cmUgRGVuaSBIZXNrb01vZHN1cHJlbUJvYm90byBCb25kZWtvIEtpbWlhRWFyLVdlbGwgTGFiTCdBcnQgZGUgVm9pck5hb3VyaSBDaXR5UGFya2luZyBaZW5wYXJrIE1hcnggRG9ybW95VHLDqW1hTGVzIElubm9jZW50c0xhIFZpZWlsbGUgUGllQ2hhdXNzZWVzIEhhdXQgZGUgR2FtbWVTdW4gVHJhdmVsTG9naW4gSW5mb3JtYXRpcXVlUmVzdGF1cmFudCBCb2RydW1FZGVzc2FEb3JhIFBob25lIFNob3BTdGFsaW5ncmFkIFBpZXJjaW5nQ2VudHJlIHNwb3J0aWYgTWljaGVsaW5lIE9zdGVybWV5ZXJDaG9oYW4gVGVsZWNvbUdsb2JhbCBHU01QYXJhZGlzIGQnQWZyaXF1ZVbDqXTDqXJpbmFpcmUgQ3JpbcOpZUp1aWN5ICYgVGFzdHlLaWxvZ3JhbW1lTGUgVGl2YW91YW5lVHphcmFTIFMgQkQgQ2xhc3NpYyBLaGFkaWphICYgQnJvc0VycmFuY2VzRWwgTm9wYWxMZSBSYWxseWVBZ2VuY2UgZGUgdm95YWdlcyBIYW5uYSBUb3Vyc1NpbW9u4oCZcyBib3V0aXF1ZSBob3RlbETDqWNvcmFzb2xMYXZlcmllQWxwaGEgQmV0YSBQcmVzc2VMYSBjYXZlIMOgIGNpZ2FyZXNTRlJDcmltw6llIC0gT3VyY3FEZSBMJ0F1dHJlIEPDtHTDqSBkZSBsYSBCdXR0ZVNtYXJ0Q2FybGEgQmVhdXTDqUxhIFRyaW5jYW50ZUZvb2RpUmV0cm9tb3Rpb25KdW1ib1NtYXJ0IE11bHRpbWVkaWFNYW0nQXlva2FTb2Npw6l0w6kgQ2FwcmljZSBDb21lc3RpcXVlUmF5YW5HYWxlcmllIEJvaG8gQm9ow6htZUUuTGVjbGVyYyByZWxhaXNZYW1haGFNYWdhc2luIGQnQWNjZXNzb2lyZXMgcG91ciBUw6lsw6lwaG9uZXNNYW1hIEFmcmljYUZldWlsbGUgw6AgRmV1aWxsZUxhIENvcm5lIGRlIGzigJlBZmZyaXF1ZUF5bmdhcmFuIENlbnRlckwnQWJzdXJkZSBJbXBvc3R1cmVNaWMgTWFjTWVkaWNhIFZpc2lvblBIIE9wdGljQydlc3QgdG91dCB2dURhaWx5IFNob3BwaW5nIENhc2ggJiBDYXJyeUJlYXV0eSBRdWVlbjfDqG1lIGLigJlhcnRLZW56aWVMZSBSZXBhaXJlTmljb2xhc0dhbGVyaWUgV2VuZ2VQb2l2cm9uIFJvdWdlQWTDqXF1YXRCaWpvdXRlcmllIE9yIEZsYW5kcmVCcmFnYXJkRmxhbmRyZSBBbGltZW50YXRpb25BdSByZW5kZXotdm91cyBkZXMgY2hhdWZmZXVyc0NoZXogUGHDr0xlIFNpbXBsb25NYWdhc2luIGRlIFdheEphcmRpbiBhdXggNCBTYWlzb25zQWxpbWVudGF0aW9uIE1lZGl0ZXJyYWluZUwnQWZmYW3DqVNDUyBJbmZvcm1hdGlxdWVNYXJpb24gR3JhdVNBUkwgRGplbmVib3UgLSBBbGltZW50YXRpb24gR8OpbsOpcmFsZVRlYW0gUFNHIFN0cmVldCBBcnRCb3V5Z3VlcyBUZWxlY29tRXVybyBRdWluY2FpbGxlcmllT3JkZW5lciAtIFBvaXNzb25uaWVyc0NlbnRyZSBzcG9ydGlmIFRyaXN0YW4gVHphcmFMJ09jY2l0YW5lQmVhdXTDqSBEaXZpbmVQcm9tZXRvdXJNYWNoaWNhZG91VkdHYXJhZ2UgQXJjaGVyZWF1Uy5TLiBDYXNoIGFuZCBDYXJyeUjDtHRlbCBNZXJyeWxNb2luY2hlcnBhcmZ1bUxlIE1hcmNow6kgRXhvdGlxdWVMZSBKYXJkaW4gZGUgRGplbmFPcHRpJyBDbGFpcmVBbGFpbiBBZmZsZWxvdUxlcyBDZXJjbGVzIGRlIGxhIEZvcm1lQml6eidhcnRFdG9pbGUgVmVydGVCaWpvdXRlcmllICsgT3JTdGFyIEFmcm8gTG9va1RpbiBUaW5BbGltZW50YXRpb24gR8OpbsOpcmFsZSBQcm9kdWl0cyBBZnJpY2FpbnNCaW8gQycgQm9uWW9uZ0NhZsOpIENhY2jDqUplYW4tQ2xhdWRlIEJpZ3VpbmVTdGFydC1VcCBWb3lhZ2VzUGFyaXMgUHJpbWV1cnMgQ3LDqG1lcmllSXR6aWtBdSBGaWwgZHUgVmluUGFyaXNsYW5kSmFycmEgQ291dHVyZUwnRXNjYWxlTGUgQm9saXZhckNoZW5uYWlDxZN1ciBkZSBOYXR1cmVDaGV6IENsYXJhT3JhbmdlUGl6emEgRmxhdm9yTGUgTG9mdE1pY2hlbGFuZ2Vsb0TDqWNvdXZyaXIgUGFyaXNCQSBQYXJpc09wdGlxdWUgU2VjcmV0YW5QaGFybWFjaWUgZGUgbCdPdXJjcUZhbW91cyBQYXJpc0tpbmdzIFRyYXZlbHNMYSBIdWNoZSBOb3JtYW5kZUxlIER1bmhpbGxDb21wdG9pciBkdSBCYW5nYWxFeGlzTGF2ZXJpZSBNIEdTYXJsIE1hYXZheUlzdGFuYnVsIE9yaWVudGFsRzIwUGhhcm1hY2llIEV1cm9ww6llbm5lQ2hleiBMYWhvdXNzaW5lTGVzIGTDqWxpY2VzIGRlIFJvc2EgUGFya3NQaGFybWFjaWUgZHUgU3F1YXJlTmV3IFRoYWkgU2FuT2tyb3MgVHVuZGVKIFdlbGxWb3lhZ2VzIExhZmF5ZXR0ZUFsbG8gUGl6emFTdXDDqXJldHRlIEFmcmljYWluZVBvaXNzb24gZHUgTW9uZGVRdWF0cmUtVmluZ3QgRGV1eFTDqWzDqSBNw6luYWdlciBQYXJpc2llblRhbmdlciAtIFBsYWNlIGR1IE1hcm9jR2FzdG9uIFRlc3NpZXIgLSBSb3NhIFBhcmtzIFJFUlBoYXJtYWNpZSA3NUNvbmZvcnR1bWFCZWUnIFNob2Vzw4lsb2RpZUV1cm8gQmFuZ2xhIE11bHRpIFNlcnZpY2VzTGUgU2FpbnQtQmVub2l0RGVwYXJ0ZW1lbnQgLSBQYWpvbFBhcmFkaXNlIFBpenphTXlocmEgVHJvcGljYWxDaXR5IFZpc2lvbkxhIFBhbnRow6hyZSBPc2XDiXBpY2VyaWVBYmR1bGxhIEtleWFhbi5jb20gVGF4aXBob25lTGVzIGNvdWxldXJzIGQnw45sZSBkZSBGcmFuY2VOZWVsdW1TdGFyIENvbU1hbWFnYXlhSMO0dGVsIEliaXMgU3R5bGVzIFBhcmlzIENyaW3DqWUgTGEgVmlsbGV0dGVMYSBSb3NlcmFpZUNlbnRyZSBkZSBraW7DqXNpdGjDqXJhcGllIFJpcXVldEJyaWNrdG9wIFBpenphU2VudGV1ciBkZXMgVmllc05hdHVyYWxpYUxlIEphc21pbiBDYWbDqUxlIFBldGl0IENhcmlsbG9uRmFicmlxdWUgRGFpZ3JlbW9udEF1YmVyZ2UgZHUgQm9uaGV1ckNvdXJzIGRlcyBIYWxsZXNEw6lqw6AgVnVlUGF0aXN0b3JpQ2F0aHkgT25nbGVzQm91Y2hlcmllIE1lbmd1ZWxsZXRKYXJkaW5zIGQnRW9sZUthcnRoaWthIEludGVybmF0aW9uYWxLb2JhbCBDYXNoIGFuZCBDYXJyeUNow6J0ZWF1IE1hcnRpYWxDb3JuZWlsIDM2Qm91bGFuZ2VyaWUgRGUgQ3JpbcOpZU5hb21pZSBCZWF1dMOpWXZlcyBSb2NoZXJSZXN0YXVyYW50IExvY29tb3RpdmVMZSBSYXRhcG9pbCBkdSBGYXVib3VyZ1RhbmcgRnLDqHJlc0xhIENhc3Nlcm9sZUxSQiBNZWRpYXRlbGVjb21MdXhvcHRpY0xlIENow6J0ZWF1IExhbmRvblNoYWFlQ2hpY2tlbiBLaW5nQnJhc3NlcmllIEwnT2xpdmVOb3RhTGVzIEFuaW1hbHNEaWFieS1LYWJhUGhvbmUgMjAwMEF1IGRlbGEgZHUgUENDaGV6IEZvdWNoZXIgTcOocmUgZXQgRmlsbGUyMjVDYXNpbm9HdW1ibyBZYXlhSGFpciBGYXNoaW9uIFN0eWxlQmFucXVlIFBvcHVsYWlyZSBSaXZlcyBkZSBQYXJpc0Nhc2EgUm9tYU5haWxzIE1pbnV0ZUpvaG4gUGF1bEVhc3QgQnVua2VyTWFyY2jDqSBHb3VybyBkJ0FiaWRqYW7DgCAyIFBhc1Njb290ZXIgQ2VudGVyIFBhcmlzSMO0dGVsIGRlIEJlbGxldnVlIFBhcmlzw4lnbGlzZSBTYWludC1TYXZhQ0hJQ0ggS0VCQUJMJ2VzY2FsZSBwb3VyIGxhIFBhaXhMZSBEamVubmUgSUlCcmlnaHQgMTAgT3B0aXF1ZVNreSBQaG9uZVBoYXJtYWNpZSBDb2hlbiBTb2xhbENhcnJlZm91ciBNYXJrZXRBbGwgSW4gUGhvbmVDb2NjaW5lbGxlT3B0aWNhbCBTZXJ2aWNlIE8rQmVhdXR5IEdpcmxzSGFub3VtYW5QaGFybWFjaWUgVHVyZ290UC5ULlMuIFNBUkxSdWUgZGUgRmxhbmRyZSAoY2VudHJlIGNvbW1lcmNpYWwpS0lLTyBNaWxhbm9DZW50cmUgZGUgc2FudMOpIFBhcmlzIDE4w6htZUxlIFJvYmluZXQgZCdPckZveWVyIGQnw6l0dWRpYW50cyAtIENoZW1pbiBOZXVmTWFyaWUgRnJhbmNlIFByZXQgYSBwb3J0ZXJDR0VEIC0gY29tcHRvaXIgZCfDqWxlY3RyaWNpdMOpIFZhbG15R2FyYWdlIFBham9sQ2x1YiBSQVRQRWwgTXVuZG9BYmkgQmVhdXR5IFBhbGFjZUF1eCBTcG9ydHNXaWxsaWFtJ3MgT3BlcmFMZSBHcmFuZCBQYXJxdWV0UGV1Z2VvdCBQU0EgUmV0YWlsIFBhcmlzIEdhcmUgZGUgbCdFc3RBbnRhbHlhSmFyZGluIMOpcGjDqW3DqHJlIEJhdWRlbGlxdWVNYWdhc2luIGRlIFRpc3N1c1NlcGhvcmFTb3VsIEZvb2RLYWxpZW50ZURhcm91c2FsYW0gQ291dHVyZUjDtHRlbCBSaXF1ZXRIw7R0ZWwgUGFyaXMgVmlsbGV0dGVQw6hyZSAmIEZpc2hDaGV6IEFtbWFkUGhhcm1hY2llIExhYnJlbHlDYXNpbm8gU2hvcFBoYXJtYWNpZSBCYW5hTWFyY2hhbmRlcyBkZSBjb3VsZXVyc0ZyZXNoICYgRmFzdEdyaWxsIElzdGFuYnVsTGUgTmF2aWdhdGV1ck15IEJydXNoQm9iJ3MgQmFrZSBTaG9wQm91dGlxdWUgUkFUUExhIFJhbWVNb25vcOKAmURhaWx5QWlkYSBCZWF1dMOpw4lwaWNlcmllIGRlcyBFbnZpcm9uc0dhcmUgZGUgbCdFc3QgLSBWZXJkdW5TbmFjayAxMTZHcmFuZCBHYXJhZ2UgQ2xpZ25hbmNvdXJ0TGEgVmllIGVzdCBCZWxsZUJWIENvbmdvIEV4b3RpcXVlUGlzY2luZSBkZXMgQW1pcmF1eER5bmFtb0wnYXZlbnR1cmUgw6AgVsOpbG9TYWxvbiBDaGFudGFsTWV1YmxlcyBQYXNjYWxUYWJhY0ZseW5hc0Jpc3Ryb3QgUGFwaWxsb25MYSBCYWd1ZXR0ZUTDqWxpY2VzIGRlIEZsYW5kcmVGcmFuY2UgQmFuZ2xhTGEgQ2FudGluYUxlIE1hdGhpc1BhcmlzIE5vcmQgR1NNTydSb3lhbENhaXNzZSBkJ8OJcGFyZ25lIFBhcmlzIE9yZGVuZXIgTWFpcmllQnJpY28gTWV1Ymxlc08nVnJhYyBkJ0Ftw6lsaWVDb3B5IFByZXNzw4lnbGlzZSBTYWludC1EZW55cyBkZSBsYSBDaGFwZWxsZU1hbGFMYSBNYWlzb24gQmxldWVUYXNza2FHYXJlIGRlIGwnRXN0IC0gQ2jDonRlYXUtTGFuZG9uSGFkaSBDb2lmZnVyZUxlIE5hcnZhbFNBUkwgTWFkaWJhQXV0b3VyIGR1IEZvdXJuaWxNdWx0aW1lZGlhIFNlcnZpY2VzSMO0dGVsIFRvcmN5TWFra2FsIEthZGFpUmVzaWRob21lIFBhcmlzIFJvc2EgUGFya3NIw7R0ZWwgZHUgVGVycmFnZVBvaW50IFBMZSBOZW1yb2RBa2Rlbml6UGl6emEgVGltZVZpZ25lc0xlIFBldGl0IEpvc2VwaEJyYXNzZXJpZSBMZSBDb3JhaWxKYW5ldCAtIENvbGxlY3Rpb25UcmlvIEFmcmljYUFydExhYlBpenphTlNQIHNhcmxDb3V0dXJlIE1vZGUgZXQgQ3JlYXRpb25QJkwgVHJlc3Nlc1F1ZWxsaSBkaSBsYVBpc2NpbmUgSMOpYmVydFRhc3Nha2FTaXJhc2EgRW50cmVwcmlzZUdyYW5kIHN1ZENJQ09wdGljJyBhbGwgQXZlbnVlU3BhIE5pbmdqaW5nIFNhbG9uIGRlIG1hc3NhZ2VQcmVzc2luZyBMeXNtdXNEb21pbm8ncyBQaXp6YVN1ZXogQmF6YXJTdHJlZXQgQmFuZ2tvayBMb2NhbCBGb29kUmVzdGF1cmFudCBpbmRpZW4gJiBDcsOqcGVyaWVNZW1vQ290IENvdExhIFRlcnJhc3NlUsOpc2lkZW5jZSBSb215IFNjaG5laWRlckF1ZGlrYUF1IE3DqWRpdGVycmFuw6llbkhvcml6b24gVW5pQmFzaWxpcXVlIFNhaW50ZS1KZWFubmUtZOKAmUFyY01vdWphaGlyIFRyYXZlbHNMZSBwYXZpbGxvbiBhdXggZmxldXJzTGUgVmljcSBkJ0F6aXJMZXMgRMOpbGljZXMgZGVzIEZhdWJvdXJnc1BhcmZ1bWVyaWUgQnVyZGluVGFjb1NoYWtlTWFyeCBleG90aXF1ZVp1YmVpZGEgQ29zbcOpdGlxdWVzTGUgQ29tbWVyY2VQb2xhciBNYXRlcmlhdXhDYWbDqSBDYWNhaHXDqHRlw4lnbGlzZSBTYWludC1MdWNFdXJvcGUgS2ViYXBNYW5kZWVxTGVzIEZvbmR1cyBkZSBsYSBSYWNsZXR0ZVBoYXJtYWNpZSBkdSBTbXBsb25CdXJnZXIgS2luZ1BhcmtpbmcgUGFyaXMgMTlLaW1zb3VCZWxsZXZpbGxlUmFvdWwgRm9sbGVyZWF1TGV6J0FydHNQbGFjZSBkdSBDb2xvbmVsIEZhYmllblByaW50IEV4cHJlc3NQaGFybWFjaWUgSG9tw6lvcGF0aXF1ZSBNYXViZXVnZUxlIFBhY3RvbGVCZWF1dMOpIFVuaXF1ZUZvcnVtIGQnQWRqYW3DqVBob25lRXh0YXNpYU55c2FIw7R0ZWwgQmVsZm9ydFBob25lIEhvdXNlQ2hleiBMYWxsYUNvcGllIExhIENoYXBlbGxlQWdlbmNlIE15cmlhbSB2b3lhZ2VzU2FtIEtlYmFiQ2FyaWVsTGUgVmVsbGVmYXV4TGEgQmVsbGUgTWV1bmnDqHJlR3JhbWVlbiBCZW5nbGFMJ8OJY29sZSBCdWlzc29ubmnDqHJlTGUgQ2hhbnNvbm5pZXLDiWdsaXNlIE5vdHJlLURhbWUgZHUgQm9uIENvbnNlaWxEaWFtIEludGVybmF0aW9uYWwgLSBNZXJjZXJpZSBHw6luw6lyYWxlWWFsb3VuYSBCb3V0aXF1ZUxhIFJvdG9uZGUgSMOpYmVydGluZVJlc3RhdXJhbnQgUGFjaGFBdSBCb250ZW1wc0FmcmlrYSBCb2JvdG9MZXMgRm91cm5pbHMgZGUgRnJhbmNlUy5QLiBSYWp1V2FzaCduIGRyeUxlIFBoZW5peCBkJ09yUGhhcm1hY2llIENvbG9uZWwgRmFiaWVuTGUgVmVycmUgVGFxdWluRXF1aXZhbGVuemFCaWpvdXRlcmllIE5vYWhNb2RlIFBsdXNDYWbDqSBkZSBsYSBHYXJlUGFya2luZyBaZW5wYXJrIFBvcnRlIGTigJlBdWJlcnZpbGxpZXJzWWF0YWxpZVJveWFsIFJvY2hlIENob3VhcnRIUyBNdWx0aSBTZXJ2aWNlc1Byw6lmw6lyZW5jZSBDb3Ntw6l0aXF1ZVNxdWFyZSBNYXVyaWNlLUtyaWVnZWwtVmFscmltb250Qm91bGFuZ2VyaWUgVmVydGVGYWl6YSBCZWF1dHlMJ8OJY2xhdCBkZXMgTWFpbnNQaGFybWFjaWUgU291c3Nhbi1BaXplbm1hbkxlIENoYWxhbmRFeG90aXF1ZSBMS04gQWZyaWNhQW5na29yUGhhcm1hY2llIGR1IE1hcmNow6lMYSBHYXJlbnRpdGEtVHdvU0FSTCBBaXIgU3VuTWlzcyBMaXF1ZU9wdGljaWVuIE5lc3NBc2lhIFBhY2lmaWMgVHJhZGVIw7R0ZWwgTGUgQmllbnZlbnVlT2hhbGV5IFlhYWtvdkZseSBDb21tdW5pY2F0aW9uQm91Y2hlcmllIFN1cGVybWFya2V0IEhhw690acOJdG9pbGUgZCdBZnJpcXVlIFZveWFnZXNSdWUgZGUgQ2hhYnJvbE15cmlhbSBDb2lmZnVyZU1heXNhcmEgSW5zdGl0dXRUZW5uaXMgZGUgdGFibGVCb3VjaGVyaWUgR291cmF5YUphcmRpbiBwYXJ0YWfDqSBkdSBUcsOoZmxlIGQnw4lvbGVEw6lsaWNlIFBhaW5MYSBQcmluY2lwZXNzZUVsZWN0cmFBc2lhIE1hcmtldEF1IEJvbmhldXIgZGVzIG1hcmnDqXNPcHRpY2FsIERpc2NvdW50QWxwaGEgQnJpY28gU2VydmljZXNMYSBNYXJtaXRlTGUgQ2Fmw6kgUHJ1ZCdoT3V0bGV0IENyaW3DqWVTdXNoaSBDcmltw6llU2Fsb24gZGUgbWFzc2FnZSB0aGFpbGFuZGFpcyBKaWEgSmlhUnVlIExhIEZheWV0dGVIb3Jsb2dlckxlIFJpZGVhdSBkZSBGZXJGdWxhbm9IZWxsYSBNYXJpYWdlR8OpbsOpcmFsZSBkJ09wdGlxdWVDYW5hbCBTdXNoaUxhcyBDaGljYXNGYXVib3VyZyBQb2lzc29ubmnDqHJlSMO0dGVsIFZpY3EgZCdBemlyTCdBbWJhc3NhZGVHU00gV2FsbGV0QmFsYXZpbnlhZ2FyTENMQm91Y2hlcmllIEFsIEFtYW5hTGEgSGFsbGVQQyBEaWFnbm9zdGljTGEgTWFpc29uIFRoYcOvTWVnYXdheFRhdGlFY29sb2dpZSAyMDAwQ2hvdSBDb20gVG91dFRoYcOvIDE4TUFBIFRlbGVjb21MJ1VuaXZlcnMgZGUgTMOpb1RvdGFsw4lwaWNlcmllIEFmcmljYWluZUxlcyBGcmFuZ2luc0xhdmVyaWUgU0JTQXUgVGFxdWV0VHJpYW5nbGUgZCdvclRow6nDonRyZSBQaXhlbEFsZXhpc0dvcGFsIGFuZCBDbyBTdXBlcm1hcmNow6lEb3BwaW9KdSBZb3VCYXJyYWN1ZGFMYSBDb3Vwb2xlS2hhbHNhIFRleHRpbGVFY2xhdCBCZWF1dMOpUGl6emEgTmlub0FnZW5jZSBUcmFuc2lsaWVuQ2hleiBDeXJpbGxlRm9udGFpbmUgZGUgQmVsbGV2aWxsZU0uQ09NTWFya3MgYW5kIFNwZW5jZXIgRm9vZEF1IFJlbmRlei12b3VzIGR1IE1hcmlhZ2VMZSBUcmljeWNsZUZhbGllRm9udGFpbmUgV2FsbGFjZVNyaSBNYWhhbFJlc3RhdXJhbnQgTWFuZGFyaW4gZHUgTWFyY2jDqUxlIE11cmdlckjDtHRlbCBDcmltw6llSmlhbHknc1BhcmFkaXNlIFRyYXZlbHNPJ1BhcmNMZSBDb2xiZXJ0Q2VudHJlIEhwIDI1IFJldm9sdXRpb25TQVJMIERlYm9SYWptYWhhbEjDtHRlbCBkdSBQcsOpUHJlc3NpbmdsJ1VuaXZlcnMgZGUgTMOpb0jDtHRlbCBkZXMgT2x5bXBpYWRlc0JhYyDDoCBzYWJsZVDDonRpc3NlcmllIGRlcyBTdWx0YW5zS2lkcyBNYWpvck9wdGlxdWUgR3VlekTDqWNvcmF0aW9uIG1hdMOpcmlhdXggZGUgRmxhbmRyZUFmcm8tc29pbnNDcsOpZGl0IGR1IE5vcmRib3VpLWJvdWlHZW5lcmF0b3IgSG9zdGVsc0FiZG91bG1hamlkTW9ub3AnRGFpbHlTbydGb29kc1ZhbGVnZUJvdXRpcXVlIEdyYW5kZXMgTGlnbmVzaWJpcyBQYXJpcyBDYW5hbCBTYWludCBNYXJ0aW5FdmFuZ2lsZSAtIEF1YmVydmlsbGllcnNMYSBQZXJnb2xhIGQnSXRhbGlhSmFyZGluIHBhcnRhZ8OpIGRlIGxhIGJ1dHRlIEJlcmdleXJlRUxNIEluZm9ybWF0aXF1ZUFtYmFhbFN1bnNoaW5lTGEgUGFpbGxhc3NlRmllc3RhIMOoIGJhc3RhUMOpZGljdXJlRW1tYcO8cyBEw6lmaVIuQy5BIE11bHRpIFJ2aWNlc0dhbyBsaSBzYWxvbiBkZSBtYXNzYWdlTCdFdGVybmVlbFRoZSBOZXcgTG9vayBGYXNoaW9uVW4gQW1vdXIgZGUgZmxldXJEcmFmdCBBdGVsaWVyc1LDqXNpZGVuY2UgZHUgUHLDqVBhbmRhIE1hcmlhZ2VMZXMgUHJpbWV1cnMgZHUgQ2jDonRlYXVCbGFuY2hpc3NlcmllIGNvdXR1cmUgU2VjcsOpdGFuU2FiYmFoU2hhbWl0aGFOZXNzRmFzYWwgQ29pZmZ1cmVNYWlzb24gQ2"); private static final SymbolTable LARGE_ENCODED = JAVA.encode(LARGE); // 230kb private static final byte[] XLARGE; private static final SymbolTable XLARGE_ENCODED; static { XLARGE = new byte[LARGE.length * 10]; for (int i = 0; i < 10; i++) { System.arraycopy(LARGE, 0, XLARGE, i * LARGE.length, LARGE.length); } XLARGE_ENCODED = JAVA.encode(XLARGE); } @Benchmark public SymbolTable encodeSmallJava() { return JAVA.encode(SMALL); } @Benchmark public SymbolTable encodeMediumJava() { return JAVA.encode(MEDIUM); } @Benchmark public SymbolTable encodeLargeJava() { return JAVA.encode(LARGE); } @Benchmark public SymbolTable encodeExtraLargeJava() { return JAVA.encode(XLARGE); } @Benchmark public SymbolTable encodeSmallJni() { return JNI.encode(SMALL); } @Benchmark public SymbolTable encodeMediumJni() { return JNI.encode(MEDIUM); } @Benchmark public SymbolTable encodeLargeJni() { return JNI.encode(LARGE); } @Benchmark public SymbolTable encodeExtraLargeJni() { return JNI.encode(XLARGE); } @Benchmark public byte[] decodeSmallJava() { return JAVA.decode(SMALL_ENCODED); } @Benchmark public byte[] decodeMediumJava() { return JAVA.decode(MEDIUM_ENCODED); } @Benchmark public byte[] decodeLargeJava() { return JAVA.decode(LARGE_ENCODED); } @Benchmark public byte[] decodeExtraLargeJava() { return JAVA.decode(XLARGE_ENCODED); } } ================================================ FILE: java/mlt-core/src/jmh/java/org/maplibre/mlt/data/rle_PartOffsets.csv ================================================ 2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;3;3;2;3;3;3;3;3;2;2;3;2;2;2;3;2;2;3;2;3;3;2;2;3;3;3;3;3;2;2;2;2;3;3;3;2;2;2;3;2;3;3;2;2;2;2;3;3;2;2;2;2;2;3;2;2;2;3;2;2;3;2;2;2;3;3;2;2;3;2;2;3;2;3;2;3;2;2;2;2;2;2;2;2;2;2;3;3;3;3;3;3;3;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;3;2;3;2;3;2;2;2;2;2;2;2;3;2;2;2;2;2;2;3;3;3;2;2;3;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;4;3;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;3;2;3;2;3;2;2;2;2;2;2;3;3;3;2;3;2;2;2;2;2;2;2;2;3;2;2;2;3;3;2;2;2;2;2;2;2;3;3;3;3;3;2;3;3;2;2;3;2;2;2;3;2;2;3;3;3;3;2;2;3;2;2;3;3;3;3;3;3;2;2;3;2;2;3;3;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;3;2;2;2;2;2;3;2;2;2;2;2;2;2;3;2;2;2;3;2;2;3;2;3;2;2;2;2;3;3;2;2;2;2;2;2;3;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;3;2;2;2;3;3;3;3;3;3;3;3;3;3;3;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;3;3;3;2;2;2;2;2;3;3;3;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;3;2;2;2;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;3;3;3;3;2;3;2;3;2;2;2;3;2;2;2;2;2;3;3;2;3;2;2;3;2;2;2;3;2;2;2;2;2;2;2;3;3;2;2;3;3;2;3;2;2;2;2;2;2;2;3;2;2;3;3;3;3;2;3;3;2;3;2;3;3;2;2;3;2;2;2;2;3;3;3;2;2;2;2;3;2;2;3;2;2;3;3;2;2;3;2;2;2;3;2;3;2;3;2;2;3;3;3;3;3;3;2;3;3;2;2;3;3;2;2;3;2;2;2;2;2;3;3;2;2;2;3;2;2;2;3;3;2;2;2;2;3;3;2;3;2;2;3;3;3;2;2;2;2;2;2;3;3;3;3;3;3;2;2;2;3;3;3;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;3;2;3;2;2;3;3;2;2;3;3;3;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;3;3;3;3;2;3;2;2;2;2;2;2;3;3;2;3;4;2;2;3;3;3;3;3;2;2;2;2;2;3;2;2;2;3;2;2;3;3;3;2;2;2;2;3;2;2;3;3;3;3;3;3;2;2;2;2;2;2;3;2;2;3;2;3;2;3;2;2;2;2;2;2;3;2;2;2;2;2;3;3;3;2;2;3;2;2;3;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;3;2;2;2;3;3;2;2;3;3;3;3;2;2;3;2;2;3;2;2;2;3;2;2;3;2;3;3;3;2;2;2;2;3;3;2;3;3;3;2;3;2;2;2;2;2;2;2;3;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;3;3;3;3;3;2;2;2;2;2;2;3;3;3;3;3;3;3;2;3;3;2;3;2;2;2;2;2;2;3;2;2;3;3;2;3;3;3;3;3;2;2;2;2;3;3;2;2;2;2;2;2;3;3;3;3;2;2;2;2;2;2;3;2;2;3;2;2;3;3;2;2;3;3;2;3;2;2;2;2;2;2;2;3;3;3;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;3;2;2;2;2;3;2;2;2;2;2;3;2;3;2;3;3;2;3;2;2;3;3;3;3;2;2;2;2;3;3;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;3;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;3;3;2;2;2;2;2;2;2;2;3;2;2;3;3;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;3;3;3;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;3;2;3;2;3;2;2;3;2;2;2;3;2;2;2;3;3;2;2;3;2;2;2;2;2;3;2;2;2;2;3;3;2;3;3;2;2;3;2;2;2;2;3;2;2;3;3;3;3;4;3;3;2;2;2;2;2;2;2;3;2;2;3;3;3;2;2;3;2;2;3;3;2;2;3;2;2;2;2;3;2;2;3;2;2;2;2;3;3;2;2;2;2;3;2;2;2;3;3;2;2;2;2;2;2;2;3;3;2;2;3;2;2;3;3;2;2;3;2;3;2;3;2;2;2;2;2;2;3;2;2;2;2;3;3;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;2;3;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;3;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;6;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;3;2;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;4;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;5;2;3;2;2;2;2;2;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;4;2;3;2;2;2;3;2;2;2;2;2;3;2;3;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;7;2;2;2;2;2;2;2;2;2;2;2;2;2;2;6;2;2;2;2;2;2;3;2;4;2;2;2;4;2;4;3;3;2;2;3;2;2;2;4;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;3;3;2;2;3;2;2;2;2;2;2;2;2;2;4;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;3;2;2;2;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;5;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;3;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;4;2;3;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;3;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;3;4;2;4;3;2;2;2;2;2;2;2;2;2;2;6;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;4;2;2;2;2;2;2;2;2;2;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;4;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;5;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;4;2;2;4;2;4;3;5;2;3;4;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;4;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;4;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;3;2;3;3;2;2;7;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;5;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;7;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;3;2;2;2;2;2;2;2;3;2;2;2;2;3;3;3;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;4;2;2;3;2;3;2;2;2;2;4;2;2;3;2;2;2;2;2;2;2;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;4;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;3;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;3;2;2;2;2;2;2;3;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;5;4;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;4;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;4;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;3;2;3;2;2;2;2;2;2;3;2;2;2;3;3;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;3;2;2;3;3;3;2;3;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;4;2;3;2;2;2;2;4;2;2;2;2;4;2;2;2;3;2;2;2;2;2;3;2;3;2;2;2;2;3;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;3;3;3;3;3;2;2;2;3;2;2;4;2;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;3;3;2;2;2;2;3;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;3;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;5;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;6;2;2;2;5;4;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;4;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;3;7;5;3;4;2;2;3;3;4;3;5;3;3;3;6;3;10;2;3;3;2;3;3;3;2;3;3;2;2;7;6;4;4;2;2;3;2;5;2;3;2;4;4;4;2;2;2;2;2;3;15;3;3;7;6;3;4;9;7;5;3;2;3;3;5;3;5;3;3;5;7;4;2;2;3;3;3;3;3;3;2;5;5;7;3;3;2;5;2;2;2;3;2;3;3;3;3;6;4;4;2;2;3;3;2;5;5;4;15;5;2;2;3;5;4;2;3;2;2;3;3;2;2;2;2;2;2;2;3;7;5;4;9;6;3;5;3;3;3;3;3;3;3;6;3;10;3;5;3;4;3;3;7;3;5;3;3;3;5;3;2;2;3;3;3;2;6;7;3;3;3;2;7;3;5;5;3;3;13;5;5;3;3;4;3;3;3;3;2;5;3;3;5;2;2;2;2;4;3;3;5;3;3;4;2;5;4;3;3;3;3;3;5;3;2;4;3;3;3;8;3;3;2;2;3;5;3;2;5;3;3;3;5;5;3;3;3;3;3;3;7;2;2;5;2;2;2;5;3;3;3;5;3;5;7;3;3;3;6;3;3;3;4;2;2;3;3;4;5;9;3;7;5;3;4;3;3;8;6;2;3;6;5;2;3;5;3;7;9;3;3;3;5;2;3;5;10;3;3;3;2;2;3;3;4;3;2;2;3;3;3;2;2;3;3;2;2;2;2;3;2;2;5;2;2;3;4;3;5;5;3;3;3;3;2;2;2;2;3;3;3;4;3;3;2;3;9;6;2;5;7;2;6;3;7;5;3;2;3;2;2;4;4;2;2;3;3;3;5;3;2;3;2;4;3;3;2;5;2;2;3;5;3;3;6;2;2;5;2;2;3;3;3;5;2;3;2;2;3;3;3;3;4;3;3;3;3;7;2;2;5;2;2;3;3;7;3;3;3;3;2;2;3;3;4;2;2;3;2;4;3;3;2;3;2;4;6;3;2;2;2;2;2;2;3;2;6;3;3;3;3;3;7;3;3;5;3;3;3;3;3;3;3;2;2;2;3;2;3;4;3;3;3;2;2;2;3;2;2;2;3;5;3;3;3;4;4;4;2;6;3;3;3;3;3;3;3;3;3;2;2;2;3;2;3;7;3;2;2;2;2;3;6;3;3;4;3;3;3;2;2;3;3;3;2;3;6;3;3;3;3;3;2;2;3;3;2;2;2;2;3;3;2;2;3;3;3;3;2;2;5;3;2;2;3;3;2;2;2;3;6;3;3;3;3;3;4;3;5;3;2;2;3;3;5;6;3;3;2;5;6;5;5;5;3;3;5;3;2;2;4;10;5;4;4;4;3;4;3;3;3;7;3;3;2;4;3;3;3;3;6;7;4;8;3;4;3;11;3;3;3;3;2;2;3;3;2;2;2;3;3;3;3;3;3;2;2;4;4;3;3;5;9;3;15;3;3;3;2;2;3;3;2;2;3;2;2;4;2;3;3;3;3;3;2;2;6;5;2;2;2;2;4;3;3;7;3;8;3;2;3;3;3;3;3;3;3;3;3;3;3;5;3;2;2;3;3;3;3;2;2;3;3;3;2;3;3;3;3;3;2;2;7;4;3;5;2;13;2;6;5;11;5;3;3;2;2;8;3;9;3;3;3;5;3;2;2;2;2;2;3;3;3;2;3;5;4;3;8;3;5;5;3;2;2;4;3;3;3;3;4;3;3;3;2;3;3;3;3;2;3;3;3;5;3;3;5;3;3;3;9;4;2;2;2;2;3;7;3;2;10;7;9;3;3;3;3;3;3;3;3;3;3;3;3;3;3;2;2;3;3;4;3;3;2;3;3;4;3;3;2;3;3;3;3;3;3;2;3;3;3;2;2;3;3;3;2;2;2;2;3;3;3;2;3;2;2;2;2;3;5;7;3;3;7;2;3;3;3;2;3;3;3;5;4;4;4;2;2;3;3;3;2;3;2;3;2;2;2;3;3;3;6;3;3;3;3;3;3;3;3;3;2;2;3;4;3;3;3;4;3;3;11;4;3;2;2;5;7;3;10;3;10;3;3;2;3;4;2;2;3;2;2;3;4;3;3;3;3;3;5;3;3;5;2;2;3;8;2;2;2;3;3;4;3;3;3;5;3;3;4;2;2;3;7;8;3;3;3;3;3;3;2;2;4;3;5;2;2;3;3;2;3;3;4;3;2;5;3;3;3;4;6;3;3;5;3;3;3;4;3;3;3;3;2;5;2;2;3;3;3;3;2;3;3;5;2;3;3;3;3;3;3;3;3;10;3;4;5;3;3;7;4;3;2;3;3;6;9;3;3;3;3;4;3;4;3;3;3;3;3;5;3;2;3;8;3;3;6;3;4;5;3;2;4;5;3;2;2;2;3;2;5;3;3;3;5;7;3;3;3;5;5;3;2;3;3;4;3;3;3;7;5;4;9;2;2;2;5;3;3;5;4;7;6;3;3;3;3;4;2;2;3;2;2;3;7;2;2;3;3;3;3;3;3;3;2;3;2;3;2;2;3;2;3;3;3;3;5;3;2;2;3;3;3;2;2;2;2;2;2;2;2;6;5;10;3;6;5;3;3;3;2;2;2;3;3;2;2;2;5;3;2;2;3;3;4;3;3;3;3;3;3;2;3;3;2;2;4;7;2;2;3;2;3;3;3;3;3;4;3;3;3;3;3;8;2;3;4;5;5;3;3;3;5;2;2;2;2;2;2;5;2;3;4;3;3;5;3;2;2;3;5;3;2;3;3;2;3;2;3;2;2;3;2;3;2;5;3;3;2;3;10;3;5;2;2;4;5;3;3;3;3;2;2;2;2;3;2;2;9;3;2;2;2;2;2;2;5;3;3;3;3;2;3;2;3;3;4;3;6;3;6;5;6;3;3;3;3;2;3;3;3;3;3;3;5;3;2;3;4;3;2;2;3;4;3;5;4;2;2;2;2;2;3;2;3;3;5;3;5;3;3;2;2;2;2;3;3;3;3;2;3;3;3;3;2;2;5;2;6;2;2;3;3;3;3;3;3;3;3;4;4;3;3;4;3;5;3;3;2;2;2;3;2;3;4;3;2;3;3;3;9;3;3;5;3;3;3;3;2;2;3;3;3;2;2;5;5;3;3;5;3;3;3;4;2;4;3;3;3;5;2;2;3;3;4;2;2;3;3;3;6;3;5;2;2;3;3;2;3;2;7;2;2;3;3;3;3;3;3;3;2;2;2;3;3;4;3;3;2;2;2;3;2;3;2;3;3;3;6;3;3;3;4;3;3;3;3;5;4;5;5;3;4;5;5;2;3;2;6;2;3;3;2;2;2;3;3;2;3;3;3;3;3;2;2;2;2;3;3;3;3;2;3;5;5;8;3;4;4;3;5;4;4;3;5;5;2;5;3;3;3;6;3;3;5;5;3;2;3;3;3;4;7;2;2;5;3;2;2;2;2;2;4;3;5;3;3;6;3;6;3;4;5;3;3;3;3;3;2;3;2;2;3;2;2;3;3;3;5;3;5;3;3;3;3;3;3;7;3;2;2;2;2;3;2;5;3;3;8;2;3;3;3;2;3;3;4;3;3;3;3;3;3;3;3;4;2;4;4;3;3;3;3;2;5;3;3;3;3;3;3;2;2;3;3;5;3;3;3;3;2;2;2;2;3;4;2;3;3;7;3;3;4;3;4;5;3;5;2;2;7;2;2;2;3;2;3;3;3;4;2;3;3;5;3;2;2;2;2;2;2;3;5;9;3;3;3;2;2;7;4;3;3;3;2;3;3;3;3;3;3;3;8;4;3;3;5;5;3;5;7;3;3;2;6;2;3;2;2;4;3;4;3;3;4;3;4;3;3;3;5;3;3;3;8;3;5;3;3;3;2;4;2;2;2;3;3;3;3;3;5;3;2;2;3;3;2;2;2;2;2;3;2;5;3;3;3;2;3;5;2;3;3;3;3;2;2;2;3;3;3;3;2;2;2;3;5;5;3;7;2;2;3;3;3;4;3;3;2;2;3;5;3;3;2;2;3;3;3;2;2;3;3;2;2;2;3;3;3;3;2;2;3;2;2;2;3;3;3;3;3;2;2;2;2;4;3;3;2;2;3;3;3;2;2;2;3;2;3;2;2;3;2;3;2;2;2;2;2;2;3;3;2;2;2;2;3;2;2;3;3;3;3;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;3;3;2;3;3;3;3;2;3;2;3;2;2;2;2;2;2;2;3;2;2;2;2;2;3;2;4;5;3;3;3;3;3;3;4;4;3;4;8;2;2;3;7;6;6;4;4;3;3;4;2;2;3;3;2;5;5;2;2;3;2;2;2;6;2;2;3;4;2;2;2;2;2;3;2;2;2;2;3;4;6;2;2;3;3;11;6;6;2;3;2;4;2;2;5;3;6;3;3;2;2;2;2;7;4;3;2;2;7;3;3;3;3;5;3;4;3;3;8;2;2;6;7;5;3;3;3;3;6;4;2;2;2;2;2;2;2;7;4;5;3;2;2;2;9;4;5;3;3;3;10;5;3;3;2;3;3;2;2;6;9;10;3;4;3;5;3;4;3;6;6;3;3;3;2;2;2;3;3;2;2;2;7;2;3;3;3;3;2;2;2;3;2;3;4;3;2;3;2;3;3;3;3;3;2;2;3;2;2;2;2;2;3;2;2;6;2;2;3;6;3;3;3;2;2;6;3;2;2;3;2;3;3;2;7;3;7;5;2;3;2;2;3;8;8;3;3;3;6;3;5;4;8;8;2;2;2;3;4;4;3;8;5;7;3;3;2;2;6;4;4;5;11;3;3;3;2;3;3;2;4;3;3;3;5;3;7;3;8;5;4;4;3;2;2;8;3;3;3;3;4;3;10;3;5;5;4;7;3;3;6;5;6;2;3;4;8;3;3;4;2;2;3;3;2;2;3;3;3;3;3;3;2;2;5;3;3;2;3;3;3;3;2;6;5;7;2;3;2;2;3;3;3;3;3;3;3;3;6;2;3;3;2;2;2;2;5;3;13;5;2;2;7;12;2;2;3;6;3;3;3;2;3;5;6;3;5;3;7;4;2;2;3;3;4;2;2;2;2;3;9;4;2;11;3;7;6;2;2;2;2;3;5;5;3;3;3;5;5;3;5;4;5;7;2;3;2;2;3;2;11;10;2;5;3;4;2;5;6;4;7;3;10;3;3;6;2;2;5;4;3;2;2;11;3;3;3;3;2;2;2;2;2;3;3;9;3;2;7;4;2;3;3;3;3;2;3;4;2;2;3;3;8;3;3;3;5;3;3;3;3;2;3;3;2;2;2;2;2;3;5;2;2;3;3;3;3;3;3;3;3;5;2;5;2;2;3;3;3;2;2;2;3;4;3;5;3;4;3;3;7;3;4;5;3;3;2;2;3;5;5;3;2;3;2;2;5;3;2;2;2;2;3;3;2;2;3;3;3;2;3;3;3;3;3;3;3;3;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;3;3;2;2;2;2;2;2;3;3;3;3;2;2;3;4;3;3;3;3;7;3;2;2;2;2;3;4;3;3;3;3;2;4;3;5;2;2;2;6;7;3;3;2;2;3;2;2;4;4;4;2;2;2;2;2;3;3;3;3;3;3;4;3;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;3;3;2;2;2;3;3;3;2;2;2;3;3;3;2;3;3;3;3;2;3;2;3;4;3;3;2;3;3;2;2;3;3;2;2;2;2;2;2;2;2;2;4;2;3;2;3;2;2;2;3;2;3;3;2;2;2;2;2;2;2;2;2;2;4;2;2;3;3;3;2;5;3;2;3;3;3;5;2;2;3;3;3;2;6;3;2;2;2;2;3;2;3;2;3;3;5;5;2;2;3;3;3;3;3;3;3;3;3;3;3;3;2;2;3;2;2;4;3;2;3;2;3;3;3;6;3;2;2;2;3;3;4;3;2;2;3;2;2;3;3;2;3;3;2;2;3;3;3;3;2;2;3;3;3;2;2;4;4;3;3;2;2;3;4;3;3;3;2;2;2;3;3;3;2;2;5;3;3;5;2;2;2;3;2;2;2;2;3;3;3;3;2;2;3;6;2;2;2;3;2;2;2;4;4;3;3;6;3;3;3;4;3;3;3;2;3;3;2;2;2;4;3;2;3;2;3;7;2;2;2;3;3;3;3;2;2;3;3;3;3;5;4;3;2;2;2;2;2;2;4;3;2;2;2;2;4;3;3;3;2;5;3;5;3;3;3;2;2;2;2;2;2;2;4;5;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;3;3;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;3;5;3;3;3;3;3;5;4;3;3;3;4;2;2;3;2;5;2;3;2;2;2;2;3;4;2;5;2;2;3;3;3;2;2;2;2;2;2;2;2;3;3;3;3;3;2;2;3;3;5;2;2;2;2;3;4;3;3;7;4;3;3;4;3;3;3;6;3;2;2;2;2;3;3;5;2;4;3;3;2;2;3;2;3;5;3;5;3;6;5;3;3;5;5;4;2;3;2;2;3;3;3;5;3;5;11;3;2;3;2;2;2;2;2;3;3;3;3;3;3;4;2;2;3;4;2;2;2;2;3;7;2;2;2;2;2;2;3;2;3;3;2;2;3;2;2;3;3;2;3;3;3;4;3;3;3;4;3;3;2;3;3;4;2;2;3;2;3;3;2;3;4;2;2;5;5;4;2;2;2;2;2;4;3;4;3;2;2;2;5;3;3;3;3;3;3;2;3;2;2;2;2;3;4;7;3;2;2;3;5;3;8;2;3;4;3;6;3;3;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;7;5;8;5;3;3;3;3;3;3;6;3;4;4;2;3;3;5;3;4;2;3;2;2;3;10;3;5;2;4;8;7;2;2;2;3;3;2;2;2;2;2;2;2;3;2;3;2;4;3;3;4;3;3;3;4;4;2;4;4;2;2;3;6;2;2;2;3;3;3;3;3;2;3;3;5;2;3;2;3;4;3;7;3;3;2;2;2;3;3;2;2;2;2;2;4;3;4;3;3;3;2;2;2;2;2;2;3;3;3;3;3;3;3;3;3;3;2;3;3;3;4;2;2;2;2;3;3;2;2;3;3;2;3;3;3;5;5;3;3;4;2;3;3;3;3;2;2;2;2;3;3;3;3;4;4;2;2;3;3;2;7;3;3;3;4;3;3;3;7;2;3;4;5;5;2;2;3;4;6;3;2;2;2;3;6;2;2;5;2;2;9;3;2;2;2;2;3;7;3;3;4;2;4;3;3;10;2;2;2;3;3;3;5;2;2;2;2;2;3;3;2;2;2;2;3;2;2;3;2;3;2;2;2;2;2;2;2;2;2;3;3;3;2;2;2;3;3;3;5;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;3;3;2;2;2;2;2;2;2;3;2;2;2;2;3;2;2;3;3;2;2;2;2;3;3;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;3;2;2;2;2;3;2;2;2;2;3;4;4;3;3;3;3;2;2;2;2;2;3;2;4;3;5;4;2;3;2;2;3;3;3;3;4;3;3;2;3;2;2;2;2;2;3;3;4;3;2;2;2;3;3;3;3;3;3;4;3;3;3;2;2;3;3;2;2;2;2;2;2;2;2;3;3;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;3;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;3;2;2;2;2;3;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;3;3;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;4;2;2;3;3;3;3;3;2;2;2;2;5;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;4;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;3;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;3;2;2;2;3;3;2;2;2;2;2;2;5;5;5;4;4;2;5;2;5;3;2;2;2;7;8;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;2;2;2;3;3;2;2;2;2;2;2;2;3;3;3;2;2;2;2;3;3;2;2;3;3;3;3;3;3;3;3;2;2;3;3;3;2;2;4;2;2;3;3;2;2;3;2;2;3;3;4;3;3;2;2;2;3;3;5;4;5;3;3;2;3;2;2;3;3;2;2;3;3;4;2;3;3;2;2;2;3;3;3;3;3;3;4;2;2;4;2;3;2;3;3;3;3;3;2;3;3;2;3;2;2;2;2;3;2;2;2;3;2;2;3;3;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;3;2;2;3;6;4;3;5;3;3;2;2;2;2;2;2;3;2;2;3;3;3;3;2;2;3;3;3;3;3;4;3;2;2;3;3;4;2;3;3;3;3;3;2;2;4;3;2;2;3;2;2;3;2;2;2;3;3;3;2;2;3;2;2;4;3;2;5;5;4;3;3;3;2;3;2;2;2;2;2;3;3;3;3;2;3;2;2;2;4;3;3;4;3;3;4;2;2;3;3;5;3;3;3;2;2;2;3;2;2;3;3;2;2;2;2;2;3;3;3;3;3;2;2;2;2;3;3;3;3;3;3;3;3;3;2;2;2;2;3;3;3;3;2;3;3;3;2;2;4;2;2;2;2;3;3;3;2;3;3;3;3;3;3;3;2;2;2;3;2;3;2;3;3;3;2;2;2;2;3;3;2;2;3;3;8;3;7;3;3;2;3;3;3;2;2;2;2;2;2;3;2;2;2;2;2;3;3;5;3;3;3;2;3;2;3;3;3;3;2;3;4;2;4;3;2;2;3;3;3;5;3;3;4;3;3;2;2;2;2;2;2;2;2;3;2;2;2;3;4;3;4;3;2;2;3;3;5;3;3;2;2;2;3;3;2;2;2;3;3;3;2;2;2;3;3;3;4;5;3;3;3;3;2;2;2;2;2;2;2;2;3;6;2;2;2;3;2;2;3;3;3;2;2;2;2;3;2;2;3;2;3;2;3;2;3;3;2;2;5;3;3;3;3;3;3;2;2;2;5;7;3;2;3;4;4;5;3;4;2;2;2;2;3;2;3;3;2;2;3;3;3;3;3;3;3;3;2;2;2;3;2;2;3;3;3;3;3;3;2;3;3;3;2;3;3;2;2;2;2;2;3;3;3;5;4;2;2;2;2;3;3;2;2;3;3;2;2;2;2;4;3;3;2;3;3;4;3;3;3;3;3;2;3;3;2;2;3;3;4;6;3;3;2;5;3;3;3;3;3;3;4;3;3;3;2;2;2;2;4;2;3;3;3;2;3;4;2;2;2;2;2;2;3;3;3;3;2;2;2;2;3;3;2;2;5;3;2;2;4;3;3;2;2;3;4;2;3;2;2;4;3;3;3;3;5;2;2;2;2;3;4;4;3;3;3;3;3;3;4;3;3;2;2;3;3;7;5;3;3;3;3;3;3;3;4;3;3;3;3;3;3;2;3;3;3;2;2;3;2;2;3;2;2;2;2;2;2;2;3;3;2;2;3;3;2;3;2;2;2;3;3;3;2;3;3;3;3;3;2;3;3;3;3;2;3;3;3;3;3;3;3;3;3;2;2;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;2;3;2;3;3;3;3;3;2;2;3;3;6;5;3;6;3;4;3;3;3;3;2;2;2;2;5;2;5;5;3;4;3;3;2;3;3;2;5;5;3;3;3;3;2;2;3;3;3;3;2;2;2;2;3;2;2;3;3;3;2;2;3;3;3;3;3;2;3;3;3;3;2;2;2;3;2;2;2;3;2;2;2;3;3;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;3;2;3;3;2;2;3;3;4;3;2;3;3;2;2;3;3;2;4;2;2;3;3;2;2;2;3;2;2;2;3;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;4;3;3;3;3;3;3;3;4;3;2;2;3;3;3;2;2;2;2;3;2;2;2;3;3;2;2;2;3;2;2;2;2;2;2;2;2;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;6;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;3;2;2;2;5;3;4;2;2;2;2;2;3;2;3;3;4;2;2;3;2;2;2;2;3;2;3;4;3;3;3;3;5;3;3;3;3;3;3;3;3;2;3;3;3;2;5;2;3;2;2;2;3;3;3;3;2;5;3;5;2;2;2;4;4;3;3;3;11;3;3;4;3;2;3;2;2;2;3;3;3;3;3;3;2;7;3;3;3;2;2;2;2;3;4;2;2;2;2;3;3;3;3;3;2;2;2;4;3;2;2;2;2;2;2;3;3;3;3;2;3;3;5;3;3;2;5;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;3;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;3;3;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;3;3;3;4;3;7;3;3;3;3;3;5;3;3;2;2;3;3;3;3;2;2;2;2;2;2;5;3;3;2;3;3;5;2;3;7;2;2;4;3;4;2;3;4;3;3;3;7;2;5;3;3;3;4;3;3;3;3;3;5;5;3;4;5;6;3;5;5;2;5;6;3;2;3;7;3;2;4;2;2;2;3;3;2;2;2;5;3;3;3;2;2;2;6;3;4;3;4;2;3;7;2;4;3;6;3;5;3;3;6;3;3;3;3;2;3;3;3;4;2;3;3;2;2;2;3;2;2;2;2;2;3;2;3;6;3;5;3;5;3;3;2;3;6;3;6;3;3;3;3;2;11;3;3;6;4;3;5;2;2;4;4;3;2;2;5;3;2;3;3;2;2;3;4;3;3;4;3;5;2;2;2;2;3;3;7;3;2;2;2;2;2;5;2;3;3;3;3;11;3;3;2;2;2;2;2;5;5;2;2;3;2;2;3;3;3;3;3;2;3;2;3;2;4;2;3;2;2;3;6;2;2;3;4;3;3;2;2;5;5;2;2;4;3;5;3;3;3;2;3;3;4;5;3;3;2;2;3;3;4;5;4;2;2;2;3;2;2;2;3;3;3;3;3;3;3;3;3;2;2;3;2;3;2;3;3;2;2;2;2;3;2;2;3;3;3;2;7;3;3;3;3;10;4;3;4;3;2;5;3;3;2;2;3;3;3;3;14;2;2;3;3;2;2;2;2;8;3;3;3;4;5;2;3;4;4;3;3;3;3;4;3;5;2;2;3;2;2;3;2;3;3;2;3;2;2;2;2;3;3;3;2;2;2;2;3;2;2;2;2;3;3;3;2;3;3;3;2;4;2;2;2;2;2;2;2;4;2;5;2;3;7;3;6;2;3;3;2;3;3;3;2;2;2;3;3;6;5;5;3;2;2;4;2;2;2;6;3;3;2;3;3;7;3;3;3;2;2;3;3;2;2;2;3;2;2;2;4;3;2;3;2;2;2;2;2;2;2;5;3;3;3;3;3;3;3;5;3;2;3;4;2;3;2;3;2;2;2;2;3;3;3;2;3;3;2;2;2;2;2;2;2;2;2;3;4;3;3;3;3;9;4;3;2;2;3;3;5;3;2;2;2;2;2;3;3;2;2;4;5;3;2;2;3;2;3;3;3;2;3;7;3;4;2;2;3;2;3;3;3;4;3;3;2;2;5;5;2;3;2;3;4;3;3;3;3;3;5;4;3;3;6;3;3;3;5;2;2;2;2;2;3;2;3;3;3;3;3;3;2;2;2;2;3;2;3;3;3;2;2;5;3;3;2;5;7;3;2;5;3;3;3;7;4;2;2;2;3;3;2;2;2;3;3;3;3;3;2;3;3;3;3;2;3;3;3;3;2;4;4;3;3;2;3;2;2;4;3;3;3;8;3;2;2;2;2;2;2;3;2;2;3;2;2;5;3;3;2;2;2;3;4;2;4;5;5;5;3;3;3;3;3;2;2;3;3;2;2;2;3;3;2;3;2;2;3;2;2;2;2;2;4;2;3;2;3;4;2;2;2;2;3;2;2;3;2;2;3;2;2;5;3;2;2;3;4;2;3;3;6;2;2;3;2;3;2;2;2;2;3;4;2;2;3;3;2;2;2;3;2;2;3;3;2;8;3;2;3;3;3;5;2;2;2;3;4;2;2;2;2;2;2;2;2;2;3;11;2;2;2;2;2;4;3;2;3;10;3;2;2;3;2;4;3;4;4;2;2;3;2;2;4;2;2;3;3;2;2;2;3;3;3;3;3;4;3;3;2;3;2;3;2;2;2;5;3;2;2;2;7;4;2;3;2;2;2;2;2;2;2;2;2;3;7;3;4;3;2;2;5;3;5;3;3;3;3;5;2;3;3;3;2;2;4;2;2;2;2;5;2;2;2;2;2;7;5;3;3;3;3;4;3;5;3;3;3;2;2;5;3;3;2;3;3;2;5;4;5;6;2;2;3;2;8;5;4;10;16;9;3;2;2;2;3;2;2;2;4;3;4;3;5;3;2;2;3;2;2;2;4;3;3;3;9;4;3;2;3;3;2;3;2;2;2;2;2;2;2;5;2;3;2;2;2;3;5;5;3;15;3;3;2;2;2;2;2;3;3;2;2;5;6;3;3;3;2;2;3;3;9;3;5;5;3;3;3;2;8;2;2;2;2;3;3;2;2;2;2;3;4;5;8;3;2;3;2;3;2;4;4;3;3;2;2;2;2;3;2;3;2;2;3;2;3;2;2;3;2;2;3;7;2;2;3;5;2;2;3;2;3;2;2;3;2;3;2;2;2;3;2;3;4;10;2;2;4;8;3;3;5;3;5;3;5;3;4;2;3;2;3;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;5;3;5;2;3;2;3;2;5;3;2;2;2;3;2;2;2;3;3;3;3;3;3;2;2;4;5;2;2;3;3;2;3;3;4;3;3;4;5;2;3;2;7;3;3;3;2;2;2;2;2;2;3;3;3;3;5;3;3;3;7;3;5;5;3;3;2;2;3;4;3;3;3;3;2;2;3;2;2;2;3;3;3;3;2;3;3;2;9;3;2;2;2;2;2;3;2;2;3;3;3;3;3;3;2;3;4;7;3;5;3;7;5;3;3;3;3;4;3;4;3;3;3;3;4;3;3;2;4;2;2;4;3;3;3;3;3;3;5;3;2;2;2;4;3;3;6;3;3;2;3;2;3;4;6;5;3;3;3;4;13;2;2;3;6;2;2;2;3;3;3;3;3;3;3;3;2;2;5;3;2;2;3;2;2;2;3;3;3;3;3;3;3;2;2;3;3;3;2;3;3;5;5;3;5;2;2;2;2;3;5;5;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;3;2;2;3;2;3;4;2;4;3;3;3;3;2;2;2;2;2;2;3;3;3;5;2;2;3;2;3;2;2;3;2;2;2;2;2;2;2;2;3;2;2;3;3;2;2;2;2;3;2;2;2;2;2;3;3;2;2;2;2;2;3;2;2;2;3;3;2;2;2;2;5;3;3;3;2;2;8;3;3;3;5;5;3;3;3;3;2;2;2;4;4;4;2;3;2;2;2;2;2;2;3;3;2;2;2;2;2;2;3;2;2;2;2;2;2;3;5;5;3;4;2;4;3;3;2;2;2;2;2;2;2;3;5;2;2;3;3;4;2;2;3;2;3;3;2;2;3;2;2;2;8;3;10;3;3;4;5;3;3;3;3;2;5;2;2;5;2;2;5;3;5;5;5;3;5;4;3;2;3;3;3;2;2;2;2;3;3;2;2;3;4;2;2;2;2;3;5;2;2;2;2;3;3;4;5;2;2;2;2;5;3;2;2;2;2;3;4;3;4;2;2;4;2;2;2;2;3;3;2;2;2;2;3;3;2;2;2;2;7;3;3;2;2;2;2;2;2;4;3;3;2;5;3;3;7;6;3;2;2;3;2;2;3;3;3;3;3;3;2;2;2;2;2;3;3;3;2;3;3;3;6;3;3;4;2;2;2;2;2;2;2;2;2;2;3;5;4;2;2;2;2;2;2;2;2;2;2;2;5;2;3;3;3;2;2;2;2;2;3;2;2;3;3;3;4;8;2;2;2;5;2;2;3;2;3;3;3;2;2;3;2;3;2;2;2;2;2;2;2;2;3;3;2;2;3;3;3;3;3;2;3;5;2;14;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;3;3;2;7;5;5;3;2;4;2;2;4;3;2;2;2;3;3;3;4;3;3;3;3;4;3;3;3;2;2;2;2;2;2;2;3;3;2;4;2;2;2;2;2;2;2;3;3;2;2;4;2;2;2;2;2;2;2;2;2;4;2;3;2;2;2;2;2;2;2;4;3;2;3;2;2;3;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;3;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;2;2;5;3;2;2;11;10;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;6;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;3;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;3;2;2;2;2;3;4;2;3;2;3;2;2;2;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;3;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;3;4;2;3;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;4;3;2;2;3;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;5;4;2;4;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;3;3;2;2;2;5;5;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;4;4;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;4;2;2;2;3;3;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;3;2;2;2;2;4;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;4;2;2;2;2;2;3;2;2;2;2;3;3;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;3;2;3;5;5;2;3;2;2;2;2;2;3;3;2;2;2;2;3;3;3;6;3;5;4;2;2;2;2;2;2;2;2;3;2;2;3;3;3;3;5;2;2;3;2;5;3;2;2;4;3;4;3;7;5;4;5;3;4;2;5;4;5;3;3;2;2;3;3;5;2;2;3;3;2;2;2;3;2;2;2;2;2;4;3;3;3;3;8;2;2;2;2;2;2;2;2;3;3;4;2;2;2;4;2;2;3;3;3;3;3;3;4;2;3;3;3;3;3;2;2;3;3;3;2;2;3;2;2;3;2;2;2;2;2;3;3;3;5;4;3;2;3;3;3;3;3;3;2;5;3;2;3;2;2;2;3;3;4;2;2;2;3;2;7;3;3;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;3;3;2;2;3;3;2;2;4;2;5;3;5;3;3;3;3;3;3;3;3;2;2;3;3;3;2;3;3;2;3;2;3;2;2;2;2;2;2;2;3;2;2;2;2;2;3;3;2;2;3;2;3;2;2;2;2;2;2;2;2;2;3;2;2;3;3;3;4;2;4;3;3;4;2;2;4;3;4;2;2;3;5;3;3;3;3;3;2;3;2;2;3;3;3;3;3;3;3;6;3;3;3;3;4;3;3;3;3;3;5;3;3;3;3;2;2;2;2;2;3;2;3;2;2;2;2;5;3;4;3;2;2;2;3;2;3;3;3;3;2;2;2;3;2;2;2;2;2;2;2;2;3;5;3;3;3;3;3;4;3;3;3;4;3;5;3;3;2;3;5;2;2;3;5;2;3;3;3;3;3;2;3;5;3;3;5;3;2;3;3;3;3;3;2;3;7;3;3;5;3;2;3;7;3;5;4;2;2;3;3;2;2;3;3;3;3;3;3;3;3;3;2;2;3;7;3;3;5;2;3;3;2;2;2;2;3;3;3;4;4;4;5;4;5;3;4;3;3;2;2;3;3;5;3;3;3;7;3;2;2;5;3;3;3;2;3;3;2;2;2;3;2;2;5;3;3;4;3;2;2;3;3;4;3;3;3;3;2;3;2;2;2;3;3;3;2;2;3;2;4;4;2;3;3;2;3;3;2;2;3;3;3;5;2;2;3;3;2;2;3;3;3;3;3;3;2;2;4;3;2;3;3;3;3;2;5;3;2;2;2;2;2;4;3;3;5;2;5;3;3;2;3;3;3;3;3;3;3;2;2;3;4;3;3;3;3;3;2;2;3;3;3;3;2;2;3;3;3;3;3;3;2;4;2;3;2;2;4;5;3;2;2;2;2;2;3;2;2;2;2;3;3;2;2;2;3;3;3;3;5;3;7;3;3;3;2;3;3;2;3;5;3;3;3;3;3;3;3;3;2;5;5;3;5;2;3;2;3;2;3;3;3;3;3;2;2;3;2;7;3;2;3;3;2;2;3;2;3;3;2;5;4;2;3;2;3;5;2;2;5;4;5;3;3;4;2;2;4;3;2;2;3;2;3;3;5;3;5;2;3;3;3;3;3;3;2;3;3;2;2;3;2;2;2;4;3;3;3;3;3;5;3;5;5;3;7;3;3;5;2;3;3;6;5;4;3;3;3;2;2;3;2;3;3;2;2;2;3;4;3;3;3;3;2;2;3;2;3;3;5;4;5;3;3;3;3;5;2;5;3;2;2;2;3;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;3;3;3;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;3;2;2;3;3;8;2;3;3;5;3;3;7;4;3;3;2;2;2;2;2;3;2;3;4;3;3;2;3;2;3;3;3;3;3;2;2;2;2;2;3;2;3;3;2;3;2;3;3;2;3;4;3;4;3;3;3;3;2;2;3;2;3;3;2;3;3;4;5;3;5;3;3;2;2;3;2;2;2;4;3;6;5;2;2;4;4;3;7;3;2;2;3;3;3;2;3;3;3;3;3;3;3;3;3;3;2;2;3;3;2;2;2;2;3;4;3;2;2;3;2;2;3;3;3;3;3;3;5;2;2;3;2;2;11;3;3;3;2;2;3;2;2;2;2;2;5;5;3;3;5;3;10;3;5;4;11;2;2;3;5;2;3;3;3;3;3;3;2;3;3;4;3;3;3;3;2;2;5;3;3;3;2;2;4;5;6;3;4;3;3;2;2;2;2;3;3;3;3;2;2;3;2;3;3;2;2;2;2;2;2;2;2;3;2;2;3;2;3;2;2;3;3;2;2;3;2;2;2;2;3;3;2;3;2;2;2;2;2;3;6;2;2;2;2;2;3;3;3;3;3;2;2;8;3;7;5;3;3;3;3;2;3;3;5;5;4;3;9;3;5;4;4;4;3;2;6;3;3;3;2;2;2;2;2;2;2;3;5;5;2;3;3;2;2;2;2;3;3;2;2;2;3;2;2;2;2;3;3;3;3;3;2;2;2;4;2;2;2;3;3;5;3;5;3;6;3;2;2;2;2;2;2;3;2;3;3;3;5;2;2;2;2;3;2;2;2;2;3;3;3;3;3;3;3;2;2;2;3;2;2;3;4;4;4;3;2;2;3;3;3;2;2;3;4;3;3;3;3;3;3;3;2;2;2;2;2;3;2;2;2;2;2;2;3;2;2;3;2;3;3;3;2;3;3;3;2;2;3;2;2;2;3;3;3;2;4;3;2;2;3;4;3;3;7;3;3;3;2;2;2;2;2;3;3;2;4;2;2;3;4;4;4;3;4;3;3;3;5;3;5;2;3;2;3;2;3;3;3;5;3;3;4;3;3;2;5;3;2;3;3;5;3;5;3;3;2;2;3;5;3;2;2;3;4;3;3;3;3;7;3;4;3;4;5;3;2;2;3;3;3;3;3;3;2;2;2;3;4;5;3;3;6;4;3;2;2;3;3;5;2;2;3;3;4;3;3;3;3;2;2;2;2;3;3;3;3;3;3;3;2;2;2;2;3;2;2;3;3;3;3;3;2;2;2;3;2;2;3;3;2;2;2;2;7;2;2;2;2;3;3;3;3;4;3;2;2;3;7;5;3;4;7;4;3;3;3;3;3;3;3;5;3;3;3;3;2;2;3;3;3;3;5;2;2;2;2;5;5;3;3;5;3;4;3;3;3;3;3;6;3;3;3;4;4;3;3;3;3;3;2;2;5;6;3;2;2;2;2;3;3;3;2;3;3;2;3;4;3;2;2;2;3;2;2;2;2;2;2;3;2;2;3;3;2;2;3;2;3;2;3;3;3;2;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;3;2;2;2;2;2;3;2;3;2;2;2;2;2;2;3;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;4;2;3;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;4;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;4;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;3;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;3;5;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;3;4;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;3;2;2;2;6;6;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;3;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;3;3;3;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;3;3;2;2;4;2;2;3;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;3;2;2;2;2;3;2;2;2;3;3;2;2;2;2;2;3;2;3;2;3;2;2;3;2;2;3;5;2;2;2;3;2;2;2;2;2;2;2;5;2;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;2;2;3;3;2;2;2;2;3;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;6;6;2;2;2;2;2;2;2;4;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;4;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;3;2;2;2;3;3;3;2;2;2;2;2;3;3;3;4;2;2;2;2;2;3;3;3;3;3;2;3;3;3;3;3;3;3;3;3;3;3;3;3;2;2;2;3;2;2;2;2;3;4;3;4;4;3;3;3;3;3;3;3;3;3;3;3;3;3;3;2;3;2;4;3;2;2;2;2;2;2;2;3;3;3;2;2;2;2;3;3;3;2;2;2;3;5;3;2;4;5;3;3;2;2;3;3;3;3;3;2;3;3;2;2;2;3;3;2;3;3;4;3;3;3;3;3;3;3;3;2;2;2;2;3;4;2;2;2;3;2;3;2;2;2;3;2;3;2;3;3;2;2;2;2;3;3;3;2;2;2;3;2;3;4;3;3;3;2;2;3;3;2;2;3;3;3;2;2;2;3;3;3;4;2;3;3;2;3;3;3;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;3;2;2;2;2;3;3;3;3;3;3;3;3;3;3;3;3;3;5;3;3;3;2;2;4;2;2;2;2;3;3;5;3;2;2;2;2;2;2;3;3;3;2;2;3;5;3;3;3;4;3;3;3;5;3;4;3;3;3;3;4;2;2;2;2;4;3;3;3;3;3;3;3;3;3;3;2;4;4;2;2;4;3;3;2;4;5;4;2;2;3;4;3;3;6;3;3;3;6;2;2;3;3;3;3;3;3;3;2;2;2;2;3;2;4;3;3;2;2;3;2;3;2;2;3;3;5;3;3;2;3;3;8;3;12;3;3;3;2;2;2;2;3;2;3;2;2;2;2;3;2;3;4;3;2;3;3;2;2;2;2;2;3;3;3;4;3;3;2;3;2;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;3;2;3;3;2;2;3;2;3;3;2;2;2;2;2;2;2;3;3;2;2;3;3;3;2;2;2;2;6;5;3;2;2;2;2;2;2;2;3;2;3;3;2;2;2;3;2;2;2;2;3;2;2;2;2;2;2;3;3;2;2;2;2;2;2;3;3;3;3;3;4;2;2;2;3;3;2;3;3;2;2;2;3;2;2;2;3;2;3;3;2;2;3;3;2;2;2;2;2;2;3;3;2;3;3;3;2;2;2;2;2;3;3;3;3;3;3;3;3;3;4;3;3;2;2;2;3;3;3;3;3;2;3;3;3;3;3;3;3;3;2;2;3;3;5;3;2;2;4;2;2;2;3;3;3;2;4;3;2;2;3;3;3;2;2;2;2;2;2;3;3;3;3;3;5;2;2;2;2;4;3;2;2;3;2;2;2;2;3;3;6;3;3;3;3;4;3;3;3;4;3;3;2;2;3;4;3;3;3;6;3;3;3;3;3;3;5;3;2;2;2;2;2;3;2;3;3;3;3;3;2;2;2;2;3;3;3;2;2;2;2;2;2;2;3;3;2;2;2;3;2;2;2;2;2;2;2;2;2;3;3;4;3;3;3;3;2;2;3;3;5;3;3;2;3;3;3;4;3;3;2;3;3;3;2;3;3;3;3;2;4;2;3;2;2;2;2;2;3;4;3;3;3;3;2;3;2;2;2;2;3;2;2;3;3;3;3;4;3;3;2;3;3;2;2;3;3;4;3;3;3;3;2;3;2;3;3;2;2;2;2;3;3;2;2;2;2;3;2;2;2;2;4;3;3;3;3;3;3;3;2;3;2;2;2;2;2;2;2;2;3;2;3;3;2;3;3;3;3;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;3;3;2;2;3;3;2;2;3;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;3;2;2;3;3;2;3;3;3;3;3;2;2;2;2;2;2;2;2;3;2;2;3;3;3;2;3;2;2;3;2;2;3;3;3;3;3;3;3;2;2;3;2;3;3;2;2;3;3;3;3;3;3;3;2;3;2;3;3;3;3;3;2;3;2;2;2;2;2;2;2;3;3;2;2;2;2;2;3;2;3;2;2;2;3;3;2;2;2;3;2;3;2;3;4;3;4;3;4;2;3;3;7;2;2;2;3;3;2;2;3;2;3;3;2;2;2;2;3;3;3;4;3;2;2;2;6;3;3;3;3;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;3;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;3;2;2;3;3;2;3;2;2;3;2;2;3;3;3;3;2;2;2;3;3;3;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;3;3;2;2;3;2;2;3;2;2;3;3;3;5;3;2;2;3;2;3;3;2;2;2;3;2;3;2;3;3;3;2;2;2;2;3;3;3;3;3;3;2;2;3;2;3;2;3;2;2;3;3;2;2;2;2;2;4;3;2;3;2;3;3;2;3;3;3;2;3;2;3;3;3;3;3;3;3;3;2;2;3;2;5;2;2;2;2;2;3;3;2;2;5;3;3;3;2;2;3;3;3;2;3;2;3;2;2;2;2;3;2;2;3;3;3;3;2;3;3;3;3;3;2;3;2;3;2;2;3;3;3;3;3;2;2;3;3;4;3;3;3;3;3;3;4;3;3;2;2;3;2;2;3;2;2;2;2;3;3;2;2;2;2;2;3;3;3;5;3;3;3;3;2;3;3;3;3;3;4;3;3;2;2;2;3;3;3;5;2;2;3;2;3;3;3;2;2;2;2;3;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;3;3;3;2;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;3;4;3;5;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;3;2;3;2;3;3;3;3;3;3;3;2;4;3;2;3;3;2;2;2;3;2;3;3;3;3;2;3;3;3;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;3;4;3;5;5;3;3;3;3;2;2;3;3;2;2;3;3;2;3;4;3;4;4;5;4;3;2;2;2;3;2;2;2;2;2;2;7;3;2;3;3;3;3;2;2;2;2;5;3;2;3;3;3;2;3;3;2;2;2;2;2;3;3;3;3;2;3;2;2;2;2;2;3;4;2;2;2;5;3;3;3;3;5;4;4;3;4;3;5;3;3;3;3;3;3;2;2;3;5;3;2;3;3;3;2;2;2;2;3;3;3;2;3;5;3;2;2;3;2;3;3;3;2;2;2;2;3;2;2;3;3;2;2;5;3;3;2;2;3;3;3;3;3;3;3;3;3;3;2;2;2;3;3;3;3;3;2;2;2;2;3;3;3;2;2;2;2;2;2;3;2;2;2;2;3;3;3;3;3;4;3;3;3;3;2;2;3;2;2;2;2;3;3;2;2;2;2;3;2;3;3;3;3;5;3;2;2;3;2;3;2;2;2;2;2;2;2;3;3;3;3;3;3;3;3;3;2;2;2;2;2;2;3;2;3;2;2;2;2;3;3;2;2;2;2;3;3;2;2;3;3;3;3;3;3;3;3;3;2;2;3;3;3;3;2;2;5;2;2;2;3;3;2;2;2;2;2;3;3;3;3;3;2;3;2;3;4;2;3;3;2;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;2;2;3;4;2;2;3;3;2;2;3;3;3;3;2;2;3;2;2;3;3;3;3;2;2;2;2;2;3;3;4;2;2;3;3;3;2;3;3;3;3;3;3;2;2;3;2;3;3;3;3;3;3;3;3;3;3;3;2;2;2;2;3;3;7;4;3;3;3;3;3;3;3;2;2;2;3;3;3;3;3;3;3;3;3;2;3;3;2;2;2;2;2;2;2;2;3;3;2;2;5;3;3;3;2;2;3;3;2;2;3;4;3;3;3;2;2;2;3;3;3;2;2;2;2;3;2;2;3;3;2;3;3;3;2;2;2;3;2;2;2;3;2;2;3;3;5;3;3;2;2;3;2;3;2;2;3;5;3;3;3;2;3;3;3;3;2;2;2;3;3;3;3;3;3;3;3;3;3;3;2;2;3;3;3;3;3;2;3;3;3;3;3;3;4;3;3;3;3;3;3;3;2;2;3;5;3;3;3;3;2;3;2;3;3;2;2;2;2;3;2;2;2;3;2;2;3;3;4;3;3;3;4;3;3;4;2;4;2;2;3;3;3;3;3;2;2;2;2;2;2;2;3;2;3;2;5;5;2;3;3;3;3;2;2;3;3;2;2;4;3;3;3;3;3;2;2;2;2;2;2;2;2;3;2;2;3;3;3;3;3;2;2;3;3;3;3;3;2;2;3;2;2;11;3;2;2;4;5;3;3;3;5;3;11;3;3;2;2;3;3;7;3;4;2;2;4;2;3;3;2;2;2;2;2;2;2;3;2;3;2;3;3;2;2;2;2;2;2;2;2;2;2;2;3;2;3;3;2;2;2;2;3;3;2;2;2;2;2;2;3;3;3;2;2;2;3;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;3;2;3;3;3;3;3;3;3;3;3;2;5;3;3;3;8;3;2;2;2;2;5;3;2;2;3;3;3;3;2;3;2;3;2;3;3;5;3;2;3;3;6;3;2;2;2;2;5;3;2;2;2;2;2;2;5;4;4;2;2;3;2;3;2;5;2;3;5;3;2;2;2;2;4;3;5;3;6;3;3;6;5;3;6;4;3;3;3;4;4;5;4;5;5;3;7;2;7;3;3;2;3;3;2;4;5;3;2;2;3;3;4;6;2;2;2;2;2;4;5;3;5;3;5;4;4;4;2;3;4;2;6;4;3;2;3;3;3;3;5;7;9;2;2;3;3;6;8;5;4;3;3;3;3;7;5;3;3;2;2;2;2;3;3;4;5;3;3;3;5;3;2;2;2;2;4;3;2;2;3;2;2;5;2;2;2;2;8;5;3;2;2;4;8;2;2;2;2;2;3;7;12;2;3;3;9;2;3;3;3;3;3;2;2;3;2;2;4;3;3;3;2;2;4;2;2;2;2;2;3;3;2;3;3;2;2;3;3;4;2;6;3;8;5;2;2;2;2;2;2;3;2;2;2;2;4;3;2;2;3;3;3;5;3;3;7;7;3;3;3;5;2;2;2;2;2;2;3;2;3;3;2;2;5;6;4;6;3;2;2;2;2;2;3;3;3;2;2;8;2;2;3;3;4;3;2;4;4;2;2;3;2;4;2;2;6;3;4;10;3;5;3;3;2;2;3;3;3;2;3;3;2;2;2;2;5;3;6;7;3;2;5;10;3;3;3;2;3;4;3;5;2;2;2;2;2;2;15;3;9;4;5;3;3;3;2;3;2;3;3;3;2;3;3;3;2;4;5;9;2;3;3;2;3;3;3;3;3;3;9;3;2;2;3;7;3;3;2;6;5;3;2;2;2;2;3;3;4;7;5;5;5;5;6;5;3;3;3;5;3;6;3;5;3;5;4;3;3;3;3;5;7;3;3;3;3;3;2;2;2;3;3;5;3;3;5;3;5;3;3;5;3;5;5;7;3;3;3;3;2;4;3;2;3;6;3;3;2;2;6;3;5;3;2;2;3;3;3;2;3;3;6;8;5;2;2;3;3;2;2;3;6;7;4;3;3;3;3;2;2;2;2;5;7;5;3;3;4;6;5;3;3;5;5;3;4;4;5;5;3;5;2;2;3;2;3;5;3;5;6;3;3;2;9;3;3;3;4;3;3;8;2;2;3;2;2;4;3;3;3;2;3;3;3;3;3;3;3;5;3;7;2;4;3;3;5;5;3;5;3;3;2;2;3;3;5;6;3;2;5;3;2;2;3;5;2;2;3;3;8;3;8;2;2;3;3;4;3;3;6;3;3;3;3;3;3;3;4;2;3;2;3;4;3;3;2;3;3;3;2;3;6;2;2;2;2;2;2;2;2;3;2;2;6;3;4;9;4;2;2;3;7;3;6;2;2;3;4;3;6;3;3;4;2;2;2;4;2;2;3;3;5;7;4;2;2;3;2;3;3;3;3;3;3;4;3;3;6;4;2;3;2;2;2;2;2;2;3;3;3;3;3;7;2;3;3;2;3;2;3;2;3;3;5;6;4;3;3;2;3;5;2;6;3;3;3;3;3;2;3;2;2;5;2;3;3;3;3;4;2;3;2;3;3;4;3;2;3;2;2;2;6;5;3;3;3;3;3;3;3;5;2;3;3;3;2;2;3;3;2;2;2;2;2;3;2;2;2;3;3;6;3;2;2;3;5;5;3;2;2;10;3;3;3;3;3;5;5;3;2;2;3;3;5;3;2;2;4;2;4;2;2;5;3;3;2;3;2;4;3;3;2;2;4;3;6;2;2;3;4;3;4;6;6;2;2;5;2;2;2;3;3;2;2;2;2;6;3;3;7;3;3;5;3;4;3;4;3;3;3;2;3;2;2;2;3;2;4;3;5;7;3;3;3;4;6;7;3;7;3;2;3;9;3;9;4;2;3;2;2;3;3;3;2;3;2;3;3;2;3;3;3;3;3;3;6;4;3;3;2;3;4;5;3;4;3;3;2;3;4;2;6;2;2;2;5;5;3;5;4;4;3;6;3;3;3;3;6;3;3;2;3;4;3;4;5;3;3;6;5;3;2;2;3;5;2;8;6;3;5;3;4;3;3;3;3;4;3;3;3;5;6;2;2;3;3;3;4;4;6;2;2;3;2;2;2;3;2;3;2;2;3;3;2;3;2;2;3;3;2;2;2;6;4;5;2;7;4;5;3;7;2;3;2;2;2;3;2;2;3;3;3;3;2;3;4;3;4;4;12;3;2;3;7;3;3;3;3;5;3;3;2;2;2;2;2;2;2;2;2;5;2;8;3;3;6;3;3;3;8;5;5;9;3;3;2;4;3;3;3;6;3;2;2;2;2;2;2;2;2;2;2;5;2;2;4;2;2;3;2;2;2;2;2;2;2;2;3;2;3;2;2;4;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;5;2;3;3;2;3;2;3;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;3;2;2;2;2;3;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;2;2;3;2;3;2;4;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;3;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;5;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;4;4;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;3;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;4;5;2;2;2;2;2;2;2;2;2;5;2;2;2;4;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;3;2;2;3;2;2;3;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;3;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;4;3;3;3;3;3;2;2;2;4;2;2;2;3;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;4;2;2;3;2;3;5;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;3;3;2;2;2;2;2;3;2;2;2;2;2;2;2;4;2;2;2;2;2;5;2;2;2;2;2;2;3;3;2;3;2;2;6;2;2;6;2;5;3;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;3;3;2;2;3;3;2;3;3;2;2;3 ================================================ FILE: java/mlt-core/src/jmh/java/org/maplibre/mlt/data/rle_class_ratio17.csv ================================================ 0;0;0;0;0;0;0;0;0;1;-1;0;1;-1;2;0;0;0;1;0;1;0;0;-4;0;0;0;0;0;1;0;2;-3;0;1;-1;1;-1;1;0;0;0;0;-1;1;0;0;-1;1;-1;2;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;-4;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;-1;1;0;0;0;0;0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;1;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;-4;0;0;0;1;0;0;0;0;0;0;0;4;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;-1;1;0;0;0;0;0;0;0;-1;0;1;0;0;0;0;0;0;0;0;0;0;-5;0;0;0;1;-1;-1;1;1;-1;0;0;0;0;0;1;-1;0;0;0;0;0;0;-1;1;0;0;0;0;0;0;0;-1;1;0;0;0;0;1;-1;0;0;0;1;-1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;2;-2;2;-2;0;2;-2;0;0;-1;1;0;2;-2;0;0;0;0;0;0;0;0;0;2;0;-2;0;0;0;0;0;2;-2;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;-1;0;0;0;1;0;2;-2;0;0;-1;1;0;0;0;0;0;0;0;0;0;0;0;2;0;-2;-1;1;0;0;0;0;0;0;0;0;2;-2;0;0;0;1;0;-2;0;0;2;-1;0;0;0;0;0;0;0;-1;1;0;0;0;0;0;0;0;2;-2;0;0;2;-2;0;0;0;0;0;0;0;0;0;0;2;-2;2;-2;0;0;0;0;0;-1;1;0;0;0;0;0;0;0;-1;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;2;-2;0;0;0;0;0;2;-2;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;2;-2;0;0;0;0;0;0;0;0;0;0;-1;0;1;0;0;0;0;0;0;1;-1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;-1;1;0;0;0;0;2;-3;3;-2;0;-1;1;2;-2;0;2;-1;-1;0;0;0;0;-1;1;0;0;-1;1;0;-1;1;2;-2;0;0;0;0;-1;1;-1;2;-1;0;0;0;-1;1;0;0;2;-2;0;2;-2;0;0;1;-1;0;0;0;0;0;0;0;0;0;0;0;0;-1;1;0;0;0;0;-1;0;1;-1;1;0;0;0;0;-1;3;-1;-1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;2;-2;1;-1;0;0;0;0;1;1;-2;0;0;0;0;0;1;-2;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;2;-2;0;0;0;0;0;0;0;0;0;0;1;-1;0;0;0;1;-1;0;2;-2;0;0;0;0;0;0;0;0;2;-3;1;0;0;0;0;0;0;0;0;0;0;0;0;0;2;0;0;-2;2;-2;0;0;0;0;0;0;-1;1;0;0;-1;0;0;1;0;-1;0;1;0;0;0;0;0;0;-1;3;-2;0;0;0;0;2;-1;-1;0;2;-2;1;1;-2;0;-1;0;1;2;-2;0;0;1;1;-2;0;1;-1;0;0;0;0;0;-1;0;1;-1;1;0;0;0;0;0;2;-1;-1;0;0;-1;0;0;0;1;0;0;2;-2;0;-1;0;1;0;0;0;0;0;2;-2;0;0;0;2;-2;0;1;-1;0;1;-2;1;0;0;-1;2;-1;2;-2;1;-1;2;-2;-1;2;-1;0;0;0;0;0;0;1;-1;1;-1;1;-1;1;-1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;-1;1;0;0;0;0;0;0;0;2;-2;0;0;-1;0;1;0;1;-1;0;0;0;0;0;1;-1;0;0;-1;1;0;1;-1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;2;-2;0;0;0;2;-2;0;2;-1;-1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;-1;0;0;0;0;0;0;0;0;0;0;2;-2;0;0;0;0;0;0;0;0;0;0;0;0;0;-1;1;2;-2;-1;1;0;-1;1;0;0;2;-2;0;0;1;-1;0;0;0;0;0;0;0;0;0;0;0;0;-1;1;0;0;0;0;0;0;-1;1;-1;2;1;-1;-1;1;0;-1;0;0;0;0;0;0;0;0;0;0;0;1;1;-2;0;0;0;0;0;0;0;0;2;-2;1;0;-1;-1;1;1;1;-1;-1;2;0;-2;0;0;0;1;-1;0;0;0;0;0;2;-2;0;0;0;-1;0;1;0;0;0;1;-1;-1;0;3;-2;-1;1;0;2;-3;1;0;0;0;0;0;0;0;0;0;0;2;-2;1;1;-3;1;0;1;-1;0;0;2;0;-2;0;0;0;0;2;-2;0;0;-1;0;1;0;0;2;-2;-1;1;0;0;0;0;0;0;0;0;0;1;-1;0;0;0;0;0;-1;1;-1;0;0;0;0;3;-3;1;0;0;0;0;0;0;0;0;0;-1;0;2;-2;0;1;0;0;0;0;0;-1;1;-1;0;0;1;1;1;-2;0;0;0;0;1;-1;1;-2;1;0;0;-1;1;1;1;-2;-1;1;1;-1;0;2;-2;-1;1;1;-1;2;-2;0;0;-1;1;0;0;0;0;0;0;0;0;0;-1;1;0;0;0;0;0;-1;0;0;0;1;0;0;-1;1;0;0;0;0;0;0;0;-1;1;0;0;0;0;0;0;1;-1;0;0;0;0;1;-1;0;0;1;-1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;2;-2;-1;1;2;-2;0;0;0;0;0;0;2;-2;2;-2;-1;1;0;0;0;0;0;0;0;0;2;0;-2;0;1;-1;2;-1;-1;0;0;0;0;0;1;-1;1;-1;0;0;0;0;0;0;2;-2;0;0;0;0;0;0;2;-2;0;0;0;0;2;0;-2;1;-1;0;0;0;0;1;1;-2;0;0;0;0;0;2;-2;2;-2;1;1;-2;0;0;1;1;-1;-1;0;0;2;-3;0;0;1;-1;1;0;-1;2;-1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;2;0;-2;0;2;-2;0;0;0;0;0;0;0;-1;3;-3;1;2;-2;0;0;0;0;2;-2;0;-1;0;1;0;-1;1;0;0;1;-1;0;0;2;-2;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;2;-2;0;1;1;0;-2;0;2;-2;0;0;0;2;-2;0;0;0;2;-2;0;0;0;0;-1;2;-1;0;0;0;0;0;1;-1;2;0;-1;0;1;0;-3;3;-2;2;0;0;0;0;-1;1;-2;1;-1;1;-2;1;0;0;0;0;2;0;-2;0;0;0;0;0;2;-2;0;0;-1;0;0;3;-2;1;0;0;0;1;-2;2;-2;1;0;-1;0;0;-1;0;1;0;2;0;-3;0;3;-2;2;-2;2;-2;0;1;-1;2;-2;-1;1;0;0;0;1;-2;1;0;0;0;1;-1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;2;-2;0;0;0;0;0;0;0;0;0;2;-2;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;0;-1;1;-1;2;-2;0;0;0;0;0;0;0;0;0;-1;3;0;-2;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;-1;0;0;0;0;0;0;0;0;2;-2;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;-1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;2;-2;0;0;1;-1;0;-1;1;0;2;-2;0;0;0;0;0;0;1;-1;0;-1;1;1;-1;0;0;0;0;1;-1;2;-2;0;1;-1;0;2;0;-2;0;0;0;-1;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;1;-1;-1;0;1;0;0;0;2;-2;0;0;0;0;1;0;-1;2;0;0;-1;-1;0;0;0;0;0;0;0;-1;1;0;0;0;0;0;0;2;0;0;-1;-2;2;0;0;-1;0;0;0;1;0;-1;0;0;0;0;0;-1;0;1;2;-2;0;0;0;0;0;0;0;0;0;0;0;0;0;2;-1;-1;0;1;-1;0;0;0;0;0;0;0;-1;3;-2;0;0;0;0;0;0;0;0;0;0;0;-1;1;0;0;0;2;-2;0;0;0;0;0;2;-1;1;-2;0;-1;3;-3;1;-1;0;3;-3;0;1;0;0;1;1;-2;0;0;0;0;0;0;1;-1;0;0;0;0;0;0;0;1;-1;0;0;0;2;-2;0;0;0;0;1;-1;0;0;0;0;0;0;0;-1;2;-1;0;0;0;0;0;0;0;0;0;0;0;1;0;-1;0;2;-3;1;0;0;0;0;0;0;0;0;0;-1;2;-1;0;0;0;0;0;0;0;2;0;-2;0;1;0;-1;1;-1;0;0;1;-1;0;0;0;0;1;-1;0;0;0;0;0;1;0;-1;0;0;0;0;0;0;0;2;-2;0;0;0;0;2;-2;0;0;0;-1;0;1;0;0;0;0;1;0;-1;0;-1;1;0;0;1;1;-2;2;0;-2;0;1;-1;0;2;0;0;-1;-1;1;-1;-1;1;0;1;-1;2;-2;0;0;0;2;-2;2;-2;0;0;0;0;0;0;0;-1;0;0;0;0;0;0;0;0;0;0;0;1;0;-1;0;1;2;-1;1;-2;0;0;0;0;0;0;0;0;0;0;1;1;0;-1;-1;2;-2;0;2;-2;0;2;-2;1;-1;0;0;0;0;0;1;-1;0;0;0;0;0;0;0;0;0;0;0;0;0;2;-2;2;-2;0;0;0;0;0;0;0;0;0;1;0;-1;1;1;-2;-1;1;-1;1;0;0;0;0;1;-1;0;-1;1;0;0;0;0;1;-1;0;-1;1;0;0;0;0;0;0;0;0;0;0;0;0;2;-2;2;-2;2;-2;0;-1;1;0;0;0;0;0;0;0;2;-2;0;0;0;0;0;0;0;0;0;0;2;-2;0;0;0;2;-2;2;-3;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;-2;2;-1;0;0;1;-1;0;0;0;0;0;1;1;-2;0;0;1;0;-1;-1;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;-1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;1;-2;0;0;0;0;0;0;0;1;-1;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;-4;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;-1;2;-1;0;2;-2;2;-2;0;-1;1;0;0;0;-1;0;1;-1;3;0;-1;-1;-1;0;0;0;0;3;-3;1;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;-4;0;0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;5;0;0;0;1;-1;0;0;0;0;-5;0;0;1;-2;2;-1;1;-1;0;0;0;0;1;-1;0;0;0;0;0;0;0;0;-1;1;-1;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;2;-2;0;0;0;0;0;0;0;0;-1;1;0;0;1;-1;0;-1;0;1;0;0;0;0;0;0;-1;1;0;1;-1;0;0;-1;0;1;0;0;0;0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;-4;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;5;-6;0;0;1;-1;1;0;0;-1;1;0;-1;1;-1;0;2;0;0;0;0;0;0;1;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;-4;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;0;0;0;1;0;0;0;0;-2;0;2 ================================================ FILE: java/mlt-core/src/jmh/java/org/maplibre/mlt/data/rle_id_ratio22_45k.csv ================================================ 2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;5;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;3;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;3;2;2;2;2;3;2;3;2;2;2;2;2;5;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;7;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;3;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;6;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;4;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;7;7;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;5;2;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;4;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;3;3;2;2;2;2;3;3;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;3;2;3;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;5;5;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;3;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;5;2;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;3;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;3;2;2;3;3;3;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;4;4;3;4;2;2;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;3;3;3;3;2;2;2;2;2;2;2;2;5;5;2;2;3;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;3;3;2;2;2;2;2;2;2;2;2;3;3;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;4;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;5;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;3;2;2;2;2;2;2;3;3;2;2;2;2;3;3;3;3;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;3;3;4;5;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;3;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;3;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;4;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;5;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;3;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;8;8;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;3;3;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;5;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;5;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;3;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;3;2;2;2;2;3;2;3;2;2;2;2;2;5;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;7;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;3;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;6;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;4;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;7;7;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;5;2;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;4;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;3;3;2;2;2;2;3;3;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;3;2;3;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;5;5;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;3;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;5;2;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;3;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;3;2;2;3;3;3;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;4;4;3;4;2;2;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;3;3;3;3;2;2;2;2;2;2;2;2;5;5;2;2;3;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;3;3;2;2;2;2;2;2;2;2;2;3;3;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;4;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;5;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;3;2;2;2;2;2;2;3;3;2;2;2;2;3;3;3;3;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;3;3;4;5;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;3;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;3;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;4;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;5;5;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;3;2;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;8;8;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;2;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;3;2;2;3;2;2;3;3;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;4;4;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;5;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;2;2;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;2;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;3;3;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2 ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/compare/CompareHelper.java ================================================ package org.maplibre.mlt.compare; import jakarta.annotation.Nullable; import java.util.Comparator; import java.util.HashSet; import java.util.Objects; import java.util.Optional; import java.util.SequencedCollection; import java.util.Set; import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; import lombok.Builder; import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.NotNull; import org.locationtech.jts.geom.Geometry; import org.maplibre.mlt.data.Feature; import org.maplibre.mlt.data.Layer; import org.maplibre.mlt.data.MapLibreTile; import org.maplibre.mlt.data.MapboxVectorTile; import org.maplibre.mlt.data.Property; import org.maplibre.mlt.util.StreamUtil; public final class CompareHelper { private CompareHelper() {} public enum CompareMode { None, Layers, Geometry, Properties, All } @Builder public record Difference( @NotNull String message, @Nullable Integer layerIndex, @Nullable String layerName, @Nullable Integer featureIndex, @Nullable Pair<@NotNull String, @NotNull String> items, @Nullable Pair<@NotNull Geometry, @NotNull Geometry> geometries) { @Override public String toString() { final var itemStr = (items != null) ? ("MVT: " + items.getLeft() + " MLT: " + items.getRight()) : ""; final var geomStr = (geometries != null) ? ("MVT geometry:\n" + geometries.getLeft() + "\nMLT geometry:\n" + geometries.getRight()) : ""; return (message + (itemStr.isEmpty() ? "" : " " + itemStr) + (geomStr.isEmpty() ? "" : "\n" + geomStr) + ((layerIndex != null) ? (" at layer index " + layerIndex) : "") + ((layerName != null) ? (" in layer '" + layerName + "': ") : "") + ((featureIndex != null) ? (" at feature index " + featureIndex) : "")); } } /// Compare the content of MLT and MVT tiles. /// Returns a single difference, stopping immediately when the first one is found. /// @param mlTile The MLT tile /// @param mbTile The MVT tile /// @param compareMode Which parts of the tiles to compare /// @return A description of the first difference found, or an empty Optional if the tiles are // equal. public static Optional compareTiles( @NotNull MapLibreTile mlTile, @NotNull MapboxVectorTile mvTile, @NotNull CompareMode compareMode) { return compareTiles(mlTile, mvTile, compareMode, null, false); } /// Compare the content of MLT and MVT tiles. /// Returns a single difference, stopping immediately when the first one is found. /// @param mlTile The MLT tile /// @param mbTile The MVT tile /// @param compareMode Which parts of the tiles to compare /// @param layerFilter A regex pattern to filter layers by name. If null, all layers are compared. /// @param filterInvert If true, only layers *not* matching the filter are compared. /// @return A description of the first difference found, or an empty Optional if the tiles are // equal. public static Optional compareTiles( @NotNull MapLibreTile mlTile, @NotNull MapboxVectorTile mvTile, @NotNull CompareMode compareMode, @Nullable Pattern layerFilter, boolean filterInvert) { final Predicate filter = (layerFilter == null) ? x -> true : x -> layerFilter.matcher(x.name()).matches() ^ filterInvert; return compareTiles(mlTile, mvTile, compareMode, filter); } /// Compare the content of MLT and MVT tiles /// Returns a single difference, stopping immediately when the first one is found. /// @param mlTile The MLT tile /// @param mbTile The MVT tile /// @param compareMode Which parts of the tiles to compare /// @param layerFilter A filter to select which layers to compare. /// @return A description of the first difference found, or an empty Optional if the tiles are // equal. public static Optional compareTiles( @NotNull MapLibreTile mlTile, @NotNull MapboxVectorTile mvTile, @NotNull CompareMode compareMode, @NotNull Predicate layerFilter) { final var mvtLayers = mvTile.getLayerStream().filter(x -> !x.features().isEmpty()).filter(layerFilter).toList(); final var mltLayers = mlTile.getLayerStream().filter(x -> !x.features().isEmpty()).filter(layerFilter).toList(); if (mltLayers.size() != mvtLayers.size()) { final var mvtNames = mvtLayers.stream().map(Layer::name).collect(Collectors.joining(", ")); final var mltNames = mltLayers.stream().map(Layer::name).collect(Collectors.joining(", ")); return Optional.of( Difference.builder() .message("Number of layers in MLT and MVT tiles do not match") .items(Pair.of(mvtNames, mltNames)) .build()); } for (var i = 0; i < mvtLayers.size(); i++) { final var mltLayer = mltLayers.get(i); final var mvtLayer = mvtLayers.get(i); final var layerResult = compareLayer(mltLayer, mvtLayer, compareMode, i); if (layerResult.isPresent()) { return layerResult; } } return Optional.empty(); } private static Optional compareLayer( Layer mltLayer, Layer mvtLayer, @NotNull CompareMode compareMode, int layerIndex) { final var mltFeatures = mltLayer.features(); final var mvtFeatures = mvtLayer.features(); if (!mltLayer.name().equals(mvtLayer.name())) { return Optional.of( Difference.builder() .message("Layer names differ") .layerIndex(layerIndex) .items(Pair.of(mvtLayer.name(), mltLayer.name())) .build()); } return compareFeatures( mltFeatures, mvtFeatures, compareMode, layerIndex, mvtLayer.name(), true); } public static Optional compareFeatures( SequencedCollection mltFeatures, SequencedCollection mvtFeatures, @NotNull CompareMode compareMode, int layerIndex, String layerName, boolean allowFeatureSort) { if (mltFeatures.size() != mvtFeatures.size()) { return Optional.of( Difference.builder() .message("Number of features differ") .items( Pair.of(String.valueOf(mvtFeatures.size()), String.valueOf(mltFeatures.size()))) .layerIndex(layerIndex) .layerName(layerName) .build()); } // Allow features to be sorted by ID and still match if all features have IDs final var sortableIDs = allowFeatureSort && mvtFeatures.stream().allMatch(Feature::hasId) && mltFeatures.stream().allMatch(Feature::hasId); final var maybeSortedMvtFeatures = sortableIDs ? mvtFeatures.stream().sorted(Comparator.comparing(Feature::getId)) : mvtFeatures.stream(); final var maybeSortedMltFeatures = sortableIDs ? mltFeatures.stream().sorted(Comparator.comparing(Feature::getId)) : mltFeatures.stream(); return StreamUtil.zip( maybeSortedMltFeatures, maybeSortedMvtFeatures, (mltFeature, mvtFeature) -> compareFeature( mltFeature, mvtFeature, compareMode, mltFeature.getIndex(), layerName)) .filter(Optional::isPresent) .map(Optional::get) .findFirst(); } private static Optional compareFeature( Feature mltFeature, Feature mvtFeature, @NotNull CompareMode compareMode, int featureIndex, String layerName) { if (!Objects.equals(mvtFeature.idOrNull(), mltFeature.idOrNull())) { return Optional.of( Difference.builder() .message("Feature IDs differ") .layerName(layerName) .featureIndex(featureIndex) .items( Pair.of(String.valueOf(mvtFeature.getId()), String.valueOf(mltFeature.getId()))) .build()); } if (compareMode == CompareMode.Geometry || compareMode == CompareMode.All) { final var geomResult = compareGeometry(mltFeature, mvtFeature, featureIndex, layerName); if (geomResult.isPresent()) { return geomResult; } } if (compareMode == CompareMode.Properties || compareMode == CompareMode.All) { final var propResult = compareProperties(mltFeature, mvtFeature, featureIndex, layerName); if (propResult.isPresent()) { return propResult; } } return Optional.empty(); } private static Optional compareGeometry( Feature mltFeature, Feature mvtFeature, int featureIndex, String layerName) { final var mltGeometry = mltFeature.getGeometry(); final var mltGeomValid = mltGeometry.isValid(); final var mvtGeometry = mvtFeature.getGeometry(); final var mvtGeomValid = mvtGeometry.isValid(); if (mltGeomValid != mvtGeomValid) { return Optional.of( Difference.builder() .message("Geometry validity does not match") .layerName(layerName) .featureIndex(featureIndex) .items( Pair.of(mvtGeomValid ? "valid" : "invalid", mltGeomValid ? "valid" : "invalid")) .build()); } if (mvtGeomValid && !mltGeometry.equals(mvtGeometry)) { return Optional.of( Difference.builder() .message("Geometries do not match") .layerName(layerName) .featureIndex(featureIndex) .geometries(Pair.of(mvtGeometry, mltGeometry)) .build()); } return Optional.empty(); } private static boolean propertyValuesEqual(Property pa, Property pb, int featureIndex) { if (pa == null || pb == null) { return pa == null && pb == null; } final var a = pa.getValue(featureIndex); final var b = pb.getValue(featureIndex); // Try simple equality if (Objects.equals(a, b)) { return true; } if (a == null || b == null) { return false; } // Allow for, e.g., int32 and int64 representations of the same number by comparing strings return a.toString().equals(b.toString()); } private static Optional compareProperties( Feature mltFeature, Feature mvtFeature, int featureIndex, String layerName) { final var nonNullMVTKeys = mvtFeature .getPropertyStream() .filter(p -> p.getValue(featureIndex) != null) .map(Property::getName) .collect(Collectors.toSet()); final var nonNullMLTKeys = mltFeature .getPropertyStream() .filter(p -> p.getValue(featureIndex) != null) .map(Property::getName) .collect(Collectors.toSet()); // compare keys if (!nonNullMVTKeys.equals(nonNullMLTKeys)) { final var mvtKeys = getAsymmetricSetDiff(nonNullMVTKeys, nonNullMLTKeys); final var mvtKeyStr = mvtKeys.isEmpty() ? "(none)" : String.join(", ", mvtKeys); final var mltKeys = getAsymmetricSetDiff(nonNullMLTKeys, nonNullMVTKeys); final var mltKeyStr = mltKeys.isEmpty() ? "(none)" : String.join(", ", mltKeys); return Optional.of( Difference.builder() .message("Property keys do not match") .layerName(layerName) .featureIndex(featureIndex) .items(Pair.of(mvtKeyStr, mltKeyStr)) .build()); } // compare values final var unequalKeys = mvtFeature .getPropertyStream() .filter( p -> !propertyValuesEqual( p, mltFeature.findProperty(p.getName()).orElse(null), featureIndex)) .map(Property::getName) .toList(); if (!unequalKeys.isEmpty()) { final var mvtValues = unequalKeys.stream() .map( key -> key + ": " + mvtFeature .findProperty(key) .map(p -> p.getValue(featureIndex)) .map(String::valueOf) .orElse("null")) .collect(Collectors.joining(", ")); final var mltValues = unequalKeys.stream() .map( key -> key + ": " + mltFeature .findProperty(key) .map(p -> p.getValue(featureIndex)) .map(String::valueOf) .orElse("null")) .collect(Collectors.joining(", ")); return Optional.of( Difference.builder() .message("Property values do not match") .layerName(layerName) .featureIndex(featureIndex) .items(Pair.of(mvtValues, mltValues)) .build()); } return Optional.empty(); } /// Returns the values that are in set `a` but not in set `b` private static Set getAsymmetricSetDiff(Set a, Set b) { Set diff = new HashSet<>(a); diff.removeAll(b); return diff; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/CollectionUtils.java ================================================ package org.maplibre.mlt.converter; import java.util.Collection; public class CollectionUtils { private CollectionUtils() {} public static int[] unboxInts(Collection values) { int i = 0; int[] result = new int[values.size()]; for (var value : values) { result[i++] = value.intValue(); } return result; } public static long[] unboxLongs(Collection values) { int i = 0; long[] result = new long[values.size()]; for (var value : values) { result[i++] = value.longValue(); } return result; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/ColumnMapping.java ================================================ package org.maplibre.mlt.converter; import jakarta.annotation.Nullable; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.regex.Pattern; /* * In the converter it is currently possible to map a set of feature properties into a nested struct with a depth of one level. * For example a set of name:* feature properties like name:de and name:en can be mapped into a name struct. * This has the advantage that the dictionary (Shared Dictionary Encoding) can be shared among the nested columns. * */ public class ColumnMapping { public Pattern getPrefix() { return prefix; } public Pattern getDelimiter() { return delimiter; } public boolean getUseSharedDictionaryEncoding() { return useSharedDictionaryEncoding; } public boolean hasColumnNames() { return columnNames != null && !columnNames.isEmpty(); } public Collection getColumnNames() { return columnNames; } /// Construct a mapping based on common prefixes and a delimiter public ColumnMapping(Pattern prefix, Pattern delimiter, boolean useSharedDictionaryEncoding) { this(prefix, delimiter, null, useSharedDictionaryEncoding); } public ColumnMapping(String prefix, String delimiter, boolean useSharedDictionaryEncoding) { this( Pattern.compile(prefix, Pattern.LITERAL), Pattern.compile(delimiter, Pattern.LITERAL), null, useSharedDictionaryEncoding); } /// Construct a mapping based on explicit column names public ColumnMapping(Collection columnNames, boolean useSharedDictionaryEncoding) { this(null, null, columnNames, useSharedDictionaryEncoding); } private ColumnMapping( Pattern prefix, Pattern delimiter, Collection columnNames, boolean useSharedDictionaryEncoding) { this.prefix = prefix; this.delimiter = delimiter; this.useSharedDictionaryEncoding = useSharedDictionaryEncoding; this.columnNames = (columnNames == null) ? Collections.emptyList() : List.copyOf(columnNames); } public boolean isMatch(String propertyName) { if (hasColumnNames()) { return columnNames.contains(propertyName); } else { // In the prefix case, the prefix alone or a prefix+delimiter+suffix match final var prefixMatcher = prefix.matcher(propertyName); if (prefixMatcher.find()) { if (prefixMatcher.start() == 0) { final var remainder = propertyName.substring(prefixMatcher.end()); final var suffixMatcher = delimiter.matcher(remainder); return remainder.isEmpty() || (suffixMatcher.find() && suffixMatcher.start() == 0); } } return false; } } /// Find a matching column mapping among a collection of mappings grouped by layer name patterns public static @Nullable ColumnMapping findMapping( ColumnMappingConfig patternMappings, String layerName, String propertyName) { return patternMappings.entrySet().stream() .filter(entry -> entry.getKey().matcher(layerName).matches()) .map(entry -> findMapping(entry.getValue(), propertyName)) .filter(Objects::nonNull) .findFirst() .orElse(null); } /// Find a matching column mapping among a collection of mappings public static @Nullable ColumnMapping findMapping( Collection columnMappings, String propertyName) { return columnMappings.stream().filter(m -> m.isMatch(propertyName)).findFirst().orElse(null); } @Override public String toString() { if (hasColumnNames()) { return String.format( "{columns=%s dictionary=%s}", String.join(", ", columnNames), useSharedDictionaryEncoding); } else { return String.format( "{prefix=%s separator=%s dictionary=%s}", prefix, delimiter, useSharedDictionaryEncoding); } } private final Pattern prefix; private final Pattern delimiter; private final boolean useSharedDictionaryEncoding; private final Collection columnNames; } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/ColumnMappingConfig.java ================================================ package org.maplibre.mlt.converter; import java.util.LinkedHashMap; import java.util.List; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.jetbrains.annotations.NotNull; /// Specifies multiple column mappings for different layers based on regex patterns @SuppressWarnings("serial") public class ColumnMappingConfig extends LinkedHashMap> { public ColumnMappingConfig() { super(); } public static ColumnMappingConfig of( @NotNull Pattern pattern, @NotNull List columnMappings) { final var config = new ColumnMappingConfig(); config.put(pattern, columnMappings); return config; } public @Override String toString() { final var sb = new StringBuilder(); for (final var entry : this.entrySet()) { final var pattern = entry.getKey(); final var mappings = entry.getValue(); final var mappingStrings = mappings.stream().map(Object::toString).collect(Collectors.joining(", ")); sb.append(pattern).append(" : ").append(mappingStrings); } return sb.toString(); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/ConversionConfig.java ================================================ package org.maplibre.mlt.converter; import jakarta.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import lombok.Builder; import lombok.Getter; import lombok.experimental.Accessors; import org.jetbrains.annotations.NotNull; @Builder(builderClassName = "ConfigBuilder", toBuilder = true) @Accessors(fluent = true) public class ConversionConfig { @NotNull @Builder.Default @Getter private final Boolean includeIds = DEFAULT_INCLUDE_IDS; @NotNull @Builder.Default @Getter private final Boolean useFastPFOR = DEFAULT_USE_FAST_PFOR; @NotNull @Builder.Default @Getter private final Boolean useFSST = DEFAULT_USE_FSST; @NotNull @Builder.Default @Getter private final TypeMismatchPolicy typeMismatchPolicy = DEFAULT_MISMATCH_POLICY; @NotNull @Builder.Default @Getter private final Map optimizations = Map.of(); @NotNull @Builder.Default @Getter private final Boolean preTessellatePolygons = DEFAULT_PRE_TESSELLATE_POLYGONS; @NotNull @Builder.Default @Getter private final Boolean useMortonEncoding = DEFAULT_USE_MORTON_ENCODING; @NotNull @Builder.Default @Getter private final List outlineFeatureTableNames = List.of(); @Nullable @Builder.Default @Getter private final Pattern layerFilterPattern = null; @NotNull @Builder.Default @Getter private final Boolean layerFilterInvert = DEFAULT_LAYER_FILTER_INVERT; @NotNull @Builder.Default @Getter private final IntegerEncodingOption integerEncodingOption = DEFAULT_INTEGER_ENCODING; @NotNull @Builder.Default @Getter private final IntegerEncodingOption geometryEncodingOption = DEFAULT_INTEGER_ENCODING; public static class ConfigBuilder { // Allow SyntheticMltUtil to extend the builder for testing purposes public ConfigBuilder() {} } public enum TypeMismatchPolicy { COERCE, // Coerce values to string on type mismatch ELIDE, // Skip values that don't match the first type encountered FAIL // Throw an error if a type mismatch is detected (default) } public enum IntegerEncodingOption { AUTO, // Automatically select best encoding (default) PLAIN, // Force plain encoding DELTA, // Force delta encoding RLE, // Force RLE encoding (only for const streams) DELTA_RLE // Force delta-RLE encoding } public static final boolean DEFAULT_INCLUDE_IDS = true; public static final boolean DEFAULT_USE_FAST_PFOR = false; public static final boolean DEFAULT_USE_FSST = false; public static final TypeMismatchPolicy DEFAULT_MISMATCH_POLICY = TypeMismatchPolicy.FAIL; public static final boolean DEFAULT_USE_MORTON_ENCODING = true; public static final boolean DEFAULT_PRE_TESSELLATE_POLYGONS = false; public static final boolean DEFAULT_LAYER_FILTER_INVERT = false; public static final IntegerEncodingOption DEFAULT_INTEGER_ENCODING = IntegerEncodingOption.AUTO; } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/FeatureTableOptimizations.java ================================================ package org.maplibre.mlt.converter; import java.util.List; /** * Configure optimizations for a Layer * * @param allowSorting Specifies if the FeatureTable can be sorted or the specified order has to be * preserved. * @param allowIdRegeneration Specifies if the Ids can be reassigned to a feature since they have no * special meaning and only has to meet the MVT spec criteria. The value of the id SHOULD be * unique among the features of the parent layer. * @param columnMappings Mapping of flat MVT properties to a nested MLT column to apply shared * dictionary encoding among the nested columns. */ public record FeatureTableOptimizations( boolean allowSorting, boolean allowIdRegeneration, List columnMappings) {} ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/MltConverter.java ================================================ package org.maplibre.mlt.converter; import com.google.gson.Gson; import jakarta.annotation.Nullable; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.SequencedCollection; import java.util.TreeMap; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.NotNull; import org.maplibre.mlt.converter.encodings.EncodingUtils; import org.maplibre.mlt.converter.encodings.GeometryEncoder; import org.maplibre.mlt.converter.encodings.MltTypeMap; import org.maplibre.mlt.converter.encodings.PropertyEncoder; import org.maplibre.mlt.data.Feature; import org.maplibre.mlt.data.Layer; import org.maplibre.mlt.data.LayerSource; import org.maplibre.mlt.data.Property; import org.maplibre.mlt.metadata.stream.PhysicalLevelTechnique; import org.maplibre.mlt.metadata.tileset.MltMetadata; import org.maplibre.mlt.util.ByteArrayUtil; public class MltConverter { /// Create tileset metadata from source data /// Note that this method will read through all features of all layers to infer the column /// data types, nullability, etc., it's preferable to construct the metadata from a schema. /// @param layerSource The input tile to create metadata from /// @param columnMappingConfig Optional column mapping configuration /// @param includeIdIfPresent Whether to include an ID column public static MltMetadata.TileSetMetadata createTilesetMetadata( @NotNull LayerSource layerSource, @Nullable ColumnMappingConfig columnMappingConfig, boolean includeIdIfPresent) { // TODO: Allow determining whether ID is present automatically return createTilesetMetadata( layerSource, ConversionConfig.TypeMismatchPolicy.FAIL, columnMappingConfig, includeIdIfPresent); } /// Create tileset metadata from source data /// See {@link #createTilesetMetadata(LayerSource, ColumnMappingConfig, boolean)} /// @param layerSource The input tile to create metadata from /// @param columnMappingConfig Optional column mapping configuration to be applied to all layers /// @param includeIdIfPresent Whether to include an ID column /// @param typeMismatchPolicy Policy for handling type mismatches /// first type encountered is used) public static MltMetadata.TileSetMetadata createTilesetMetadata( @NotNull LayerSource layerSource, @Nullable ColumnMappingConfig columnMappingConfig, boolean includeIdIfPresent, @NotNull ConversionConfig.TypeMismatchPolicy typeMismatchPolicy) { final var config = ConversionConfig.builder().typeMismatchPolicy(typeMismatchPolicy).build(); return createTilesetMetadata(layerSource, config, columnMappingConfig, includeIdIfPresent); } /// Create tileset metadata from source data /// See {@link #createTilesetMetadata(LayerSource, ColumnMappingConfig, boolean)} /// @param layerSource The input tile to create metadata from /// @param config Optional configuration /// @param columnMappingConfig Optional column mapping configuration to be applied to all layers /// @param includeIdIfPresent Whether to include an ID column public static MltMetadata.TileSetMetadata createTilesetMetadata( @NotNull LayerSource layerSource, @Nullable ConversionConfig config, @Nullable ColumnMappingConfig columnMappingConfig, boolean includeIdIfPresent) { return createTilesetMetadata( layerSource, (config != null) ? config.typeMismatchPolicy() : null, columnMappingConfig, includeIdIfPresent); } /// Create tileset metadata from source data /// See {@link #createTilesetMetadata(LayerSource, ColumnMappingConfig, boolean)} /// @param layerSource The input tile to create metadata from /// @param typeMismatchPolicy Policy for handling type mismatches /// @param columnMappingConfig Optional column mapping configuration to be applied to all layers /// @param includeIdIfPresent Whether to include an ID column public static MltMetadata.TileSetMetadata createTilesetMetadata( @NotNull LayerSource layerSource, @NotNull ConversionConfig.TypeMismatchPolicy typeMismatchPolicy, @Nullable ColumnMappingConfig columnMappingConfig, boolean includeIdIfPresent) { final var tileset = new MltMetadata.TileSetMetadata(); tileset.featureTables = layerSource .getLayerStream() .map( layer -> createTilesetMetadata( layer, typeMismatchPolicy, columnMappingConfig, includeIdIfPresent)) .toList(); return tileset; } private static MltMetadata.FeatureTable createTilesetMetadata( @NotNull Layer layer, @NotNull ConversionConfig.TypeMismatchPolicy typeMismatchPolicy, @Nullable ColumnMappingConfig columnMappingConfig, boolean includeIdIfPresent) { final LinkedHashMap columnSchemas = new LinkedHashMap<>(); final LinkedHashMap complexPropertyColumnSchemas = new LinkedHashMap<>(); var hasId = false; var hasLongId = false; var hasNullId = false; var featureIndex = 0; for (var feature : layer.features()) { final var currentFeatureIndex = featureIndex; feature .getPropertyStream() .forEach( property -> { resolveColumnType( property, layer.name(), currentFeatureIndex, columnMappingConfig, columnSchemas, complexPropertyColumnSchemas, typeMismatchPolicy); }); if (includeIdIfPresent) { if (feature.hasId()) { hasId = true; if ((!hasLongId && feature.getId() > Integer.MAX_VALUE) || feature.getId() < Integer.MIN_VALUE) { hasLongId = true; } } else { hasNullId = true; } } featureIndex++; } for (var complexPropertyColumnScheme : complexPropertyColumnSchemas.entrySet()) { final var schema = complexPropertyColumnScheme.getValue(); final var result = resolveComplexColumnMapping(schema); final var parentName = result.getLeft(); // Each complex column scheme needs to have a unique entry name in the column map, but there // is no specific column to which it maps. For now, just ensure that the value is unique. final var column = createColumn(parentName, result.getRight()); IntStream.iterate(0, i -> i + 1) .mapToObj(i -> parentName + i) .filter(name -> !columnSchemas.containsKey(name)) .findFirst() .ifPresent(name -> columnSchemas.put(name, column)); } final var estimatedColumns = 2 + columnSchemas.size() + complexPropertyColumnSchemas.size(); final var featureTableSchema = new MltMetadata.FeatureTable(layer.name(), estimatedColumns); // If present, `id` must be the first column if (columnSchemas.values().stream().anyMatch(MltTypeMap.Tag0x01::isID)) { throw new RuntimeException("Unexpected ID Column"); } if (hasId) { featureTableSchema .columns() .add( new MltMetadata.Column( new MltMetadata.Field(MltMetadata.idFieldType(hasLongId, hasNullId)))); } // The `geometry` column is mandatory and has to be the first column after `ID` featureTableSchema .columns() .add(createComplexColumnScheme(null, false, MltMetadata.ComplexType.GEOMETRY)); // Add the remaining items in name order for consistent output. // Put complex columns after scalar columns to match old behavior. columnSchemas.values().stream() .sorted( Comparator.comparing((MltMetadata.Column c) -> c.isComplex() ? 1 : 0) .thenComparing(c -> c.getName())) .forEach(featureTableSchema.columns()::add); return featureTableSchema; } private static void resolveColumnType( @NotNull Property property, @NotNull String layerName, int featureIndex, @Nullable ColumnMappingConfig columnMappingConfig, @NotNull LinkedHashMap columnSchemas, @NotNull LinkedHashMap complexColumnSchemas, @NotNull ConversionConfig.TypeMismatchPolicy typeMismatchPolicy) { final var sourcePropertyName = property.getName(); if (property.isNested()) { throw new NotImplementedException("Nested property types are not yet supported"); } final var scalarField = property.getType().scalarType(); final var scalarType = (scalarField != null) ? scalarField.physicalType() : null; // If this property already has a column... final var previousSchema = columnSchemas.get(sourcePropertyName); if (previousSchema != null) { // Make sure the types match. // If not, coercion or nullification must be enabled, and replace // the column with a string column, if it isn't already. if (previousSchema.isScalar()) { if (previousSchema.field().type().scalarType().physicalType() != null) { final var prevPhysicalType = previousSchema.field().type().scalarType().physicalType(); if (prevPhysicalType != scalarType) { final var newSchema = checkUpgrade(previousSchema, scalarType); if (newSchema != null) { if (newSchema != previousSchema) { columnSchemas.put(sourcePropertyName, newSchema); } } else if (typeMismatchPolicy == ConversionConfig.TypeMismatchPolicy.COERCE) { if (prevPhysicalType != MltMetadata.ScalarType.STRING) { columnSchemas.put( sourcePropertyName, new MltMetadata.Column( new MltMetadata.Field( MltMetadata.scalarFieldType( MltMetadata.ScalarType.STRING, previousSchema.isNullable()), previousSchema.getName()))); } } else if (typeMismatchPolicy != ConversionConfig.TypeMismatchPolicy.ELIDE) { throw new RuntimeException( String.format( "Layer '%s' Feature index %d Property '%s' has different type: %s / %s", layerName, featureIndex, property.getName(), scalarType.name(), prevPhysicalType.name())); } return; } } } } if (columnMappingConfig != null) { final var columnMapping = ColumnMapping.findMapping(columnMappingConfig, layerName, sourcePropertyName); if (columnMapping != null) { // A mapping exists for this property. // Create the parent type and add a child type entry. final var parentColumn = complexColumnSchemas.computeIfAbsent(columnMapping, k -> createComplexColumn()); if (parentColumn.children().stream().noneMatch(c -> c.name().equals(sourcePropertyName))) { parentColumn .children() .add(createScalarFieldScheme(sourcePropertyName, true, scalarType)); } return; } } // no matching column mappings, create a plain scalar column columnSchemas.put( sourcePropertyName, createScalarColumnScheme(sourcePropertyName, true, scalarType)); } private static MltMetadata.Column checkUpgrade( MltMetadata.Column previousSchema, MltMetadata.ScalarType scalarType) { final var prevPhysicalType = previousSchema.field().type().scalarType().physicalType(); if (prevPhysicalType == MltMetadata.ScalarType.INT_32 && scalarType == MltMetadata.ScalarType.INT_64) { // Allow implicit upgrade from INT_32 to INT_64 return new MltMetadata.Column( new MltMetadata.Field( MltMetadata.scalarFieldType( MltMetadata.ScalarType.INT_64, previousSchema.isNullable()), previousSchema.getName())); } else if (prevPhysicalType == MltMetadata.ScalarType.INT_64 && scalarType == MltMetadata.ScalarType.INT_32) { // no-op // keep INT_64 return previousSchema; } else if (prevPhysicalType == MltMetadata.ScalarType.FLOAT && scalarType == MltMetadata.ScalarType.DOUBLE) { // Allow implicit upgrade from FLOAT to DOUBLE return new MltMetadata.Column( new MltMetadata.Field( MltMetadata.scalarFieldType( MltMetadata.ScalarType.DOUBLE, previousSchema.isNullable()), previousSchema.getName())); } else if (prevPhysicalType == MltMetadata.ScalarType.DOUBLE && scalarType == MltMetadata.ScalarType.FLOAT) { // no-op // keep DOUBLE return previousSchema; } return null; } /// Resolve complex column mapping by determining common prefix and adjusting child names /// @return a pair of the resolved parent column name and the adjusted complex column scheme private static Pair resolveComplexColumnMapping( MltMetadata.ComplexField column) { final var prefix = StringUtils.getCommonPrefix( column.children().stream().map(c -> c.name()).toArray(String[]::new)); if (!prefix.isEmpty()) { var children = column.children().stream() .map( child -> { final var name = child.name(); if (!name.startsWith(prefix)) { throw new RuntimeException( "Unexpected column mapping: prefix is not present"); } return new MltMetadata.Field(child.type(), name.substring(prefix.length())); }) .sorted(Comparator.comparing(f -> f.name())) .toList(); return Pair.of( prefix, new MltMetadata.ComplexField(column.physicalType(), column.logicalType(), children)); } return Pair.of(prefix, column); } public static String createTilesetMetadataJSON(MltMetadata.TileSetMetadata pbMetadata) { var root = new TreeMap(); final int version = 1; root.put("version", version); if (pbMetadata.name != null) { root.put("name", pbMetadata.name); } if (pbMetadata.description != null) { root.put("description", pbMetadata.description); } if (pbMetadata.attribution != null) { root.put("attribution", pbMetadata.attribution); } pbMetadata.minZoom.ifPresent(integer -> root.put("minZoom", integer)); pbMetadata.maxZoom.ifPresent(integer -> root.put("maxZoom", integer)); final var bounds = new ArrayList>(); if (pbMetadata.bounds.size() % 4 != 0) { throw new IllegalArgumentException("Invalid bounds length"); } for (var iterator = pbMetadata.bounds.iterator(); iterator.hasNext(); ) { final var bound = new TreeMap(); bound.put("left", iterator.next()); bound.put("top", iterator.next()); bound.put("right", iterator.next()); bound.put("bottom", iterator.next()); bounds.add(bound); } if (!bounds.isEmpty()) { root.put("bounds", bounds); } var centers = new ArrayList>(); for (int i = 0; i < (pbMetadata.center.size() / 2); ++i) { var center = new TreeMap(); center.put("longitude", 2 * i); center.put("latitude", (2 * i) + 1); centers.add(center); } if (!centers.isEmpty()) { root.put("center", centers); } return new Gson().toJson(root); } /// Write the header block for a field or column. /// Takes the values individually because, despite having mostly /// the same information, the field and column are separate types. private static void writeColumnOrFieldType( DataOutputStream stream, String name, boolean isNullable, boolean hasLongIDs, @Nullable MltMetadata.ScalarType physicalScalarType, @Nullable MltMetadata.LogicalScalarType logicalScalarType, @Nullable MltMetadata.ComplexType physicalComplexType, @Nullable MltMetadata.LogicalComplexType logicalComplexType, @Nullable SequencedCollection children) throws IOException { // We expect exactly one of these if (Stream.of(physicalScalarType, logicalScalarType, physicalComplexType, logicalComplexType) .filter(Objects::nonNull) .count() != 1) { throw new RuntimeException("Invalid Type"); } final boolean hasChildren = (children != null && !children.isEmpty()); final var typeCode = MltTypeMap.Tag0x01.encodeColumnType( physicalScalarType, logicalScalarType, physicalComplexType, logicalComplexType, isNullable, hasChildren, hasLongIDs) .orElseThrow(() -> new RuntimeException("Unsupported Type")); EncodingUtils.putVarInt(stream, typeCode); if (MltTypeMap.Tag0x01.columnTypeHasName(typeCode)) { EncodingUtils.putString(stream, name); } if (hasChildren) { EncodingUtils.putVarInt(stream, children.size()); for (var child : children) { final boolean complex = child.type().complexType() != null; final boolean logical = (complex && child.type().complexType().logicalType() != null) || (!complex && child.type().scalarType().logicalType() != null); writeColumnOrFieldType( stream, child.name(), child.type().isNullable(), /* hasLongIDs= */ false, (!complex && !logical) ? child.type().scalarType().physicalType() : null, (!complex && logical) ? child.type().scalarType().logicalType() : null, (complex && !logical) ? child.type().complexType().physicalType() : null, (complex && logical) ? child.type().complexType().logicalType() : null, complex ? child.type().complexType().children() : null); } } } /// Produce the binary tile header containing the tile metadata /// @param table The metadata to be embedded in the tile /// @param extent The extent of tile coordinates /// @return The binary header to be embedded in the tile /// @throws IOException public static byte[] createEmbeddedMetadata(MltMetadata.FeatureTable table, int extent) throws IOException { try (var byteStream = new ByteArrayOutputStream()) { try (var dataStream = new DataOutputStream(byteStream)) { EncodingUtils.putString(dataStream, table.name()); EncodingUtils.putVarInt(dataStream, extent); EncodingUtils.putVarInt(dataStream, table.columns().size()); for (var column : table.columns()) { if (column.columnScope() != MltMetadata.ColumnScope.FEATURE) { throw new RuntimeException("Vertex scoped properties are not yet supported"); } writeColumnOrFieldType( dataStream, column.getName(), column.isNullable(), column.isScalar() && column.field().type().scalarType().hasLongId(), column.getScalarType().orElse(null), column.getLogicalScalarType().orElse(null), column.getComplexType().orElse(null), column.getLogicalComplexType().orElse(null), column.getChildren().orElse(null)); } } return byteStream.toByteArray(); } } /* * Converts a collection of layers into an MLT tile * * @param sourceLayers The input layers * @param tilesetMetadata Metadata of the tile * @param config Settings for the conversion * @param tessellateSource Optional URI of a tessellation service to use if polygon pre-tessellation is enabled * @return Converted MapLibreTile as a byte array * @throws IOException */ public static byte[] encode( LayerSource sourceLayers, MltMetadata.TileSetMetadata tilesetMetadata, ConversionConfig config, @Nullable URI tessellateSource) throws IOException { return encode( sourceLayers, tilesetMetadata, config, tessellateSource, ByteArrayOutputStream::new) .toByteArray(); } /* * Converts a collection of layers into an MLT tile * * @param sourceLayers The input layers * @param tilesetMetadata Metadata of the tile * @param config Settings for the conversion * @param tessellateSource Optional URI of a tessellation service to use if polygon pre-tessellation is enabled * @param outputStreamSupplier A function producing the output stream to which the converted tile will be written. * @return The output stream to which the data was written * @throws IOException */ public static T encode( @NotNull LayerSource sourceLayers, @NotNull MltMetadata.TileSetMetadata tilesetMetadata, @NotNull ConversionConfig config, @Nullable URI tessellateSource, @NotNull Function outputStreamSupplier) throws IOException { // Convert the list of metadatas (one per layer) into a lookup by the first and only layer name // We assume that the names are unique. final var metaMap = tilesetMetadata.featureTables.stream() .collect( Collectors.toMap( t -> t.name(), table -> table, (existing, replacement) -> { throw new RuntimeException("duplicate key"); })); final var physicalLevelTechnique = config.useFastPFOR() ? PhysicalLevelTechnique.FAST_PFOR : PhysicalLevelTechnique.VARINT; final var tileBuffers = new ArrayList((int) sourceLayers.getLayerCount() * 10); for (var sourceLayer : sourceLayers.getLayers()) { final var featureTableName = sourceLayer.name(); if (config.layerFilterPattern() != null) { final var matcher = config.layerFilterPattern().matcher(featureTableName); final var isMatch = matcher.matches() ^ config.layerFilterInvert(); if (!isMatch) { continue; } } final var layerMetadata = metaMap.get(featureTableName); if (layerMetadata == null) { throw new RuntimeException("Missing Metadata"); } final var sourceFeatures = sourceLayer.features(); if (sourceFeatures.isEmpty()) { continue; } final var optimizations = Optional.ofNullable(config.optimizations()); final var featureTableOptimizations = optimizations.map(opt -> opt.get(featureTableName)); final var createPolygonOutline = config.outlineFeatureTableNames().contains(featureTableName) || config.outlineFeatureTableNames().contains("ALL"); final var result = sortFeaturesAndEncodeGeometryColumn( config, featureTableOptimizations, sourceFeatures, sourceFeatures, physicalLevelTechnique, createPolygonOutline, tessellateSource); final var sortedFeatures = result.getLeft(); final var encodedGeometryColumn = result.getRight(); final var encodedGeometryFieldMetadata = EncodingUtils.encodeVarint(encodedGeometryColumn.numStreams(), false); final var encodedPropertyColumns = encodePropertyColumns(config, layerMetadata, sortedFeatures, featureTableOptimizations); final var featureTableBodyBuffer = new ArrayList(20); if (config.includeIds()) { final var idMetadata = layerMetadata.columns().stream() .filter(MltTypeMap.Tag0x01::isID) .findFirst() .orElseThrow(); // Write ID as a 32- or 64-bit scalar depending on the flag stored in the column metadata. // The decoding assumes unsigned (no zigzag) final var rawType = idMetadata.field().type().scalarType().hasLongId() ? MltMetadata.ScalarType.UINT_64 : MltMetadata.ScalarType.UINT_32; final var scalarColumnMetadata = new MltMetadata.Column(MltMetadata.scalarFieldType(rawType, idMetadata.isNullable())); featureTableBodyBuffer.addAll( PropertyEncoder.encodeScalarPropertyColumn( scalarColumnMetadata, true, sortedFeatures, physicalLevelTechnique, config.useFSST(), config.typeMismatchPolicy() == ConversionConfig.TypeMismatchPolicy.COERCE, config.integerEncodingOption())); } featureTableBodyBuffer.add(encodedGeometryFieldMetadata); featureTableBodyBuffer.addAll(encodedGeometryColumn.encodedValues()); featureTableBodyBuffer.addAll(encodedPropertyColumns); final var metadataBuffer = createEmbeddedMetadata(layerMetadata, sourceLayer.tileExtent()); final var tag = 1; final var tagBuffer = EncodingUtils.encodeVarint(tag, false); final var tagLength = tagBuffer.length + metadataBuffer.length + ByteArrayUtil.totalLength(featureTableBodyBuffer); tileBuffers.add(EncodingUtils.encodeVarint(tagLength, false)); tileBuffers.add(tagBuffer); tileBuffers.add(metadataBuffer); tileBuffers.addAll(featureTableBodyBuffer); } final var targetStream = outputStreamSupplier.apply(ByteArrayUtil.totalLength(tileBuffers)); Objects.requireNonNull(targetStream, "Output stream supplier returned null"); return ByteArrayUtil.concat(targetStream, tileBuffers); } @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private static ArrayList encodePropertyColumns( ConversionConfig config, MltMetadata.FeatureTable featureTableMetadata, SequencedCollection sortedFeatures, @NotNull Optional featureTableOptimizations) throws IOException { final var propertyColumns = filterPropertyColumns(featureTableMetadata); @NotNull final var columnMappings = featureTableOptimizations.map(FeatureTableOptimizations::columnMappings).orElse(List.of()); return PropertyEncoder.encodePropertyColumns( propertyColumns, sortedFeatures, config.useFastPFOR(), config.useFSST(), config.typeMismatchPolicy() == ConversionConfig.TypeMismatchPolicy.COERCE, columnMappings, config.integerEncodingOption()); } @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private static Pair, GeometryEncoder.EncodedGeometryColumn> sortFeaturesAndEncodeGeometryColumn( ConversionConfig config, Optional featureTableOptimizations, SequencedCollection sortedFeatures, SequencedCollection sourceFeatures, PhysicalLevelTechnique physicalLevelTechnique, boolean encodePolygonOutlines, @Nullable URI tessellateSource) throws IOException { /* * Following simple strategy is currently used for ordering the features when sorting is enabled: * - if id column is present and ids should not be reassigned -> sort id column * - if id column is presented and ids can be reassigned -> sort geometry column (VertexOffsets) * and regenerate ids * - if id column is not presented -> sort geometry column * In general finding an optimal column arrangement is NP-hard, but by implementing a more sophisticated strategy * based on the latest academic results in the future, the compression ratio can be further improved * */ if (sourceFeatures.isEmpty()) { throw new IllegalArgumentException("No features to encode"); } final var isColumnSortable = config.includeIds() && featureTableOptimizations.map(FeatureTableOptimizations::allowSorting).orElse(false); final var allowIdRegeneration = featureTableOptimizations.map(FeatureTableOptimizations::allowIdRegeneration).orElse(false); if (isColumnSortable && !allowIdRegeneration) { sortedFeatures = sortFeaturesById(sourceFeatures).toList(); } var ids = sortedFeatures.stream().map(Feature::idOrNull).collect(Collectors.toList()); var geometries = sortedFeatures.stream().map(Feature::getGeometry).collect(Collectors.toList()); if (geometries.isEmpty()) { throw new IllegalArgumentException("No geometries to encode"); } /* Only sort geometries if ids can be reassigned since sorting the id column turned out * to be more efficient in the tests */ final var sortSettings = new GeometryEncoder.SortSettings(isColumnSortable && allowIdRegeneration, ids); /* Morton Vertex Dictionary encoding is currently not supported in pre-tessellation */ final var useMortonEncoding = config.useMortonEncoding() && !config.preTessellatePolygons(); final var geometryEncodingOption = config.geometryEncodingOption(); final var encodedGeometryColumn = GeometryEncoder.encodeGeometryColumn( geometries, physicalLevelTechnique, sortSettings, useMortonEncoding, config.preTessellatePolygons(), encodePolygonOutlines, tessellateSource, geometryEncodingOption); if (encodedGeometryColumn.geometryColumnSorted()) { sortedFeatures = ids.stream() .map( id -> sourceFeatures.stream() .filter(fe -> Objects.equals(fe.idOrNull(), id)) .findFirst() .orElseThrow()) .collect(Collectors.toList()); } if (config.includeIds() && allowIdRegeneration) { sortedFeatures = generateSequenceIds(sortedFeatures).toList(); } return Pair.of(sortedFeatures, encodedGeometryColumn); } private static List filterPropertyColumns( MltMetadata.FeatureTable featureTableMetadata) { return featureTableMetadata.columns().stream() .filter(f -> !MltTypeMap.Tag0x01.isID(f) && !MltTypeMap.Tag0x01.isGeometry(f)) .collect(Collectors.toList()); } private static Stream sortFeaturesById(Collection features) { return features.stream() .sorted(Comparator.comparing(Feature::hasId).thenComparingLong(Feature::getId)); } private static Stream generateSequenceIds(Collection features) { final var idCounter = new long[] {0}; return features.stream() .map(feature -> feature.toBuilder().id(idCounter[0]++).index(feature.getIndex()).build()); } private static MltMetadata.Column createScalarColumnScheme( String columnName, @SuppressWarnings("SameParameterValue") boolean nullable, MltMetadata.ScalarType type) { return new MltMetadata.Column( new MltMetadata.Field(MltMetadata.scalarFieldType(type, nullable), columnName)); } private static MltMetadata.Field createScalarFieldScheme( String fieldName, @SuppressWarnings("SameParameterValue") boolean nullable, MltMetadata.ScalarType type) { return new MltMetadata.Field(MltMetadata.scalarFieldType(type, nullable), fieldName); } private static MltMetadata.Column createComplexColumnScheme( @SuppressWarnings("SameParameterValue") @Nullable String columnName, @SuppressWarnings("SameParameterValue") boolean nullable, @SuppressWarnings("SameParameterValue") MltMetadata.ComplexType type) { return new MltMetadata.Column( new MltMetadata.Field(MltMetadata.complexFieldType(type, nullable), columnName)); } private static MltMetadata.ComplexField createComplexColumn() { return new MltMetadata.ComplexField(MltMetadata.ComplexType.STRUCT); } private static MltMetadata.Column createColumn( String columnName, MltMetadata.ComplexField complexField) { final var isNullable = false; // See `PropertyDecoder.decodePropertyColumn()` return new MltMetadata.Column( new MltMetadata.Field(new MltMetadata.FieldType(complexField, isNullable), columnName)); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/MortonSettings.java ================================================ package org.maplibre.mlt.converter; public final class MortonSettings { public int numBits; public int coordinateShift; public MortonSettings(int numBits, int coordinateShift) { this.numBits = numBits; this.coordinateShift = coordinateShift; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/BooleanEncoder.java ================================================ package org.maplibre.mlt.converter.encodings; import java.io.IOException; import java.util.ArrayList; import java.util.BitSet; import org.maplibre.mlt.metadata.stream.LogicalLevelTechnique; import org.maplibre.mlt.metadata.stream.PhysicalLevelTechnique; import org.maplibre.mlt.metadata.stream.PhysicalStreamType; import org.maplibre.mlt.metadata.stream.StreamMetadata; public class BooleanEncoder { private BooleanEncoder() {} /* * Combines a BitVector encoding with the Byte RLE encoding form the ORC format * */ public static ArrayList encodeBooleanStream( Boolean[] values, PhysicalStreamType streamType) throws IOException { final var valueStream = new BitSet(values.length); for (var i = 0; i < values.length; i++) { valueStream.set(i, values[i]); } final var encodedValueStream = EncodingUtils.encodeBooleanRle(valueStream, values.length); /* For Boolean RLE the additional information provided by the RleStreamMetadata class are not needed */ final var result = new StreamMetadata( streamType, null, LogicalLevelTechnique.RLE, LogicalLevelTechnique.NONE, PhysicalLevelTechnique.NONE, values.length, encodedValueStream.length) .encode(); result.add(encodedValueStream); return result; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/ByteRleEncoder.java ================================================ package org.maplibre.mlt.converter.encodings; import java.io.ByteArrayOutputStream; import java.io.IOException; /** * Encodes byte data using run-length encoding. * *

The encoding format uses a control byte followed by data: * *

    *
  • Control byte 0x00-0x7F: Run of (control + 3) copies of the next byte *
  • Control byte 0x80-0xFF: (256 - control) literal bytes follow *
*/ public class ByteRleEncoder { static final int MIN_REPEAT_SIZE = 3; static final int MAX_LITERAL_SIZE = 128; static final int MAX_REPEAT_SIZE = 127 + MIN_REPEAT_SIZE; private final ByteArrayOutputStream output; private final byte[] literals = new byte[MAX_LITERAL_SIZE]; private int numLiterals = 0; private boolean repeat = false; private int tailRunLength = 0; public ByteRleEncoder() { this.output = new ByteArrayOutputStream(); } private void writeValues() throws IOException { if (numLiterals != 0) { if (repeat) { output.write(numLiterals - MIN_REPEAT_SIZE); output.write(literals[0] & 0xFF); } else { output.write(-numLiterals); output.write(literals, 0, numLiterals); } repeat = false; tailRunLength = 0; numLiterals = 0; } } public void write(byte value) throws IOException { if (numLiterals == 0) { literals[numLiterals++] = value; tailRunLength = 1; } else if (repeat) { if (value == literals[0]) { numLiterals++; if (numLiterals == MAX_REPEAT_SIZE) { writeValues(); } } else { writeValues(); literals[numLiterals++] = value; tailRunLength = 1; } } else { if (value == literals[numLiterals - 1]) { tailRunLength++; } else { tailRunLength = 1; } if (tailRunLength == MIN_REPEAT_SIZE) { if (numLiterals + 1 == MIN_REPEAT_SIZE) { repeat = true; numLiterals++; } else { numLiterals -= MIN_REPEAT_SIZE - 1; writeValues(); literals[0] = value; repeat = true; numLiterals = MIN_REPEAT_SIZE; } } else { literals[numLiterals++] = value; if (numLiterals == MAX_LITERAL_SIZE) { writeValues(); } } } } public void flush() throws IOException { writeValues(); } public byte[] toByteArray() throws IOException { flush(); return output.toByteArray(); } /** Convenience method to encode a byte array. */ public static byte[] encode(byte[] values) throws IOException { ByteRleEncoder encoder = new ByteRleEncoder(); for (byte value : values) { encoder.write(value); } return encoder.toByteArray(); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/DoubleEncoder.java ================================================ package org.maplibre.mlt.converter.encodings; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.maplibre.mlt.metadata.stream.LogicalLevelTechnique; import org.maplibre.mlt.metadata.stream.PhysicalLevelTechnique; import org.maplibre.mlt.metadata.stream.PhysicalStreamType; import org.maplibre.mlt.metadata.stream.StreamMetadata; public class DoubleEncoder { private DoubleEncoder() {} public static ArrayList encodeDoubleStream(List values) throws IOException { // TODO: add encodings -> RLE, Dictionary, PDE final double[] doubleArray = new double[values.size()]; for (int i = 0; i < values.size(); i++) { doubleArray[i] = values.get(i); } final var encodedValueStream = EncodingUtils.encodeDoublesLE(doubleArray); final var result = new StreamMetadata( PhysicalStreamType.DATA, null, LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, PhysicalLevelTechnique.NONE, values.size(), encodedValueStream.length) .encode(); result.add(encodedValueStream); return result; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/EncodingUtils.java ================================================ package org.maplibre.mlt.converter.encodings; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Collection; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import me.lemire.integercompression.Composition; import me.lemire.integercompression.FastPFOR; import me.lemire.integercompression.IntWrapper; import me.lemire.integercompression.IntegerCODEC; import me.lemire.integercompression.VariableByte; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.tuple.Pair; import org.maplibre.mlt.converter.CollectionUtils; public class EncodingUtils { // https://github.com/bazelbuild/bazel/blob/6ce603d8/src/main/java/com/google/devtools/build/lib/util/VarInt.java /** Maximum encoded size of 32-bit positive integers (in bytes) */ public static final int MAX_VARINT_SIZE = 5; /** maximum encoded size of 64-bit longs, and negative 32-bit ints (in bytes) */ public static final int MAX_VARLONG_SIZE = 10; public static byte[] gzip(byte[] buffer) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); GZIPOutputStream gzipOut = new GZIPOutputStream(baos); gzipOut.write(buffer); gzipOut.close(); baos.close(); return baos.toByteArray(); } public static byte[] unzip(byte[] buffer) throws IOException { try (var inputStream = new ByteArrayInputStream(buffer)) { try (var gZIPInputStream = new GZIPInputStream(inputStream)) { return gZIPInputStream.readAllBytes(); } } } /** Convert the floats to IEEE754 floating point numbers in Little Endian byte order. */ public static byte[] encodeFloatsLE(float[] values) { var buffer = ByteBuffer.allocate(values.length * 4).order(ByteOrder.LITTLE_ENDIAN); for (var value : values) { buffer.putFloat(value); } return buffer.array(); } /** Convert the doubles to IEEE754 floating point numbers in Little Endian byte order. */ public static byte[] encodeDoublesLE(double[] values) { var buffer = ByteBuffer.allocate(values.length * 8).order(ByteOrder.LITTLE_ENDIAN); for (var value : values) { buffer.putDouble(value); } return buffer.array(); } // Source: // https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/util/VarInt.java public static byte[] encodeVarints(int[] values, boolean zigZagEncode, boolean deltaEncode) throws IOException { var encodedValues = values; if (deltaEncode) { encodedValues = encodeDeltas(values); } if (zigZagEncode) { encodedValues = encodeZigZag(encodedValues); } var varintBuffer = ByteBuffer.wrap( new byte[Arrays.stream(encodedValues).map(EncodingUtils::getVarIntSize).sum()]); for (var value : encodedValues) { putVarInt(value, varintBuffer); } return varintBuffer.array(); } public static byte[] encodeVarints(long[] values, boolean zigZagEncode, boolean deltaEncode) throws IOException { var encodedValues = values; if (deltaEncode) { encodedValues = encodeDeltas(values); } if (zigZagEncode) { encodedValues = encodeZigZag(encodedValues); } var varintBuffer = ByteBuffer.wrap( new byte[Arrays.stream(encodedValues).mapToInt(EncodingUtils::getVarLongSize).sum()]); for (var value : encodedValues) { putVarInt(value, varintBuffer); } return varintBuffer.array(); } public static byte[] encodeVarints( Collection values, boolean zigZagEncode, boolean deltaEncode) throws IOException { return encodeVarints(CollectionUtils.unboxInts(values), zigZagEncode, deltaEncode); } public static byte[] encodeLongVarints(long[] values, boolean zigZagEncode, boolean deltaEncode) throws IOException { return encodeVarints(values, zigZagEncode, deltaEncode); } public static byte[] encodeVarint(int value, boolean zigZagEncode) throws IOException { if (zigZagEncode) { value = encodeZigZag(value); } final var buffer = ByteBuffer.wrap(new byte[getVarIntSize(value)]); return putVarInt(value, buffer).array(); } // Source: // https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/util/VarInt.java /** * Encodes an integer in a variable-length encoding, 7 bits per byte, into a destination byte[], * following the protocol buffer convention. * * @param v the int value to write to sink * @param sink the sink buffer to write to */ public static ByteBuffer putVarInt(int v, ByteBuffer sink) throws IOException { do { // Encode next 7 bits + terminator bit final int bits = v & 0x7F; v >>>= 7; sink.put((byte) (bits + ((v != 0) ? 0x80 : 0))); } while (v != 0); return sink; } static ByteBuffer putVarInt(long v, ByteBuffer sink) throws IOException { do { // Encode next 7 bits + terminator bit final long bits = v & 0x7F; v >>>= 7; sink.put((byte) (bits + ((v != 0) ? 0x80 : 0))); } while (v != 0); return sink; } @SuppressWarnings("UnusedReturnValue") public static DataOutputStream putVarInt(DataOutputStream stream, int v) throws IOException { final var buffer = ByteBuffer.wrap(new byte[MAX_VARINT_SIZE]); putVarInt(v, buffer); stream.write(buffer.array(), 0, buffer.position()); return stream; } private static final int DATA_BITS_PER_ENCODED_BYTE = 7; public static int getVarIntSize(int value) { final var bitsNeeded = Integer.SIZE - Integer.numberOfLeadingZeros(value); return Math.max(1, (bitsNeeded + DATA_BITS_PER_ENCODED_BYTE - 1) / DATA_BITS_PER_ENCODED_BYTE); } public static int getVarLongSize(long value) { final var bitsNeeded = Long.SIZE - Long.numberOfLeadingZeros(value); return Math.max(1, (bitsNeeded + DATA_BITS_PER_ENCODED_BYTE - 1) / DATA_BITS_PER_ENCODED_BYTE); } @SuppressWarnings("UnusedReturnValue") public static DataOutputStream putString(DataOutputStream stream, String s) throws IOException { final var bytes = s.getBytes(StandardCharsets.UTF_8); putVarInt(stream, bytes.length); stream.write(bytes); return stream; } public static long[] encodeZigZag(long[] values) { long[] result = new long[values.length]; for (int i = 0; i < values.length; i++) { result[i] = encodeZigZag(values[i]); } return result; } public static int[] encodeZigZag(int[] values) { int[] result = new int[values.length]; for (int i = 0; i < values.length; i++) { result[i] = encodeZigZag(values[i]); } return result; } public static long encodeZigZag(long value) { return (value << 1) ^ (value >> 63); } public static int encodeZigZag(int value) { return (value >> 31) ^ (value << 1); } public static long[] encodeDeltas(long[] values) { var deltaValues = new long[values.length]; var previousValue = 0L; for (var i = 0; i < values.length; i++) { var value = values[i]; deltaValues[i] = value - previousValue; previousValue = value; } return deltaValues; } public static int[] encodeDeltas(int[] values) { var deltaValues = new int[values.length]; var previousValue = 0; for (var i = 0; i < values.length; i++) { var value = values[i]; deltaValues[i] = value - previousValue; previousValue = value; } return deltaValues; } /** * @return Pair of runs and values. */ public static Pair encodeRle(int[] values) { var valueBuffer = new ArrayList(); var runsBuffer = new ArrayList(); var previousValue = 0; var runs = 0; for (var i = 0; i < values.length; i++) { var value = values[i]; if (previousValue != value && i != 0) { valueBuffer.add(previousValue); runsBuffer.add(runs); runs = 0; } runs++; previousValue = value; } valueBuffer.add(values[values.length - 1]); runsBuffer.add(runs); return Pair.of(CollectionUtils.unboxInts(runsBuffer), CollectionUtils.unboxInts(valueBuffer)); } /** * @return Pair of runs and values. */ // TODO: merge this method with the int variant public static Pair encodeRle(long[] values) { var valueBuffer = new ArrayList(); var runsBuffer = new ArrayList(); var previousValue = 0L; var runs = 0; for (var i = 0; i < values.length; i++) { var value = values[i]; if (previousValue != value && i != 0) { valueBuffer.add(previousValue); runsBuffer.add(runs); runs = 0; } runs++; previousValue = value; } valueBuffer.add(values[values.length - 1]); runsBuffer.add(runs); return Pair.of(CollectionUtils.unboxLongs(runsBuffer), CollectionUtils.unboxLongs(valueBuffer)); } public static byte[] encodeFastPfor128(int[] values, boolean zigZagEncode, boolean deltaEncode) { /* * Note that this does not use differential coding: if you are working on sorted lists, * you should first compute deltas, @see me.lemire.integercompression.differential.Delta#delta * */ var encodedValues = values; if (deltaEncode) { encodedValues = encodeDeltas(values); } if (zigZagEncode) { encodedValues = encodeZigZag(encodedValues); } IntegerCODEC ic = new Composition(new FastPFOR(), new VariableByte()); IntWrapper inputoffset = new IntWrapper(0); IntWrapper outputoffset = new IntWrapper(0); final int[] compressed = new int[encodedValues.length + 1024]; ic.compress(encodedValues, inputoffset, encodedValues.length, compressed, outputoffset); final var totalSize = outputoffset.intValue() * 4; final var compressedBuffer = new byte[totalSize]; var valueCounter = 0; for (var i = 0; i < totalSize; i += 4) { var value = compressed[valueCounter++]; compressedBuffer[i] = (byte) (value >>> 24); compressedBuffer[i + 1] = (byte) (value >>> 16); compressedBuffer[i + 2] = (byte) (value >>> 8); compressedBuffer[i + 3] = (byte) value; } return compressedBuffer; } public static byte[] encodeByteRle(byte[] values) throws IOException { return ByteRleEncoder.encode(values); } public static byte[] encodeBooleanRle(BitSet bitSet, int numValues) throws IOException { var presentStream = bitSet.toByteArray(); /* The BitSet only returns the bytes until the last set bit */ var numMissingBytes = (int) Math.ceil(numValues / 8d) - (int) Math.ceil(bitSet.length() / 8d); if (numMissingBytes != 0) { var paddingBytes = new byte[numMissingBytes]; Arrays.fill(paddingBytes, (byte) 0); presentStream = ArrayUtils.addAll(presentStream, paddingBytes); } return EncodingUtils.encodeByteRle(presentStream); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/FloatEncoder.java ================================================ package org.maplibre.mlt.converter.encodings; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.maplibre.mlt.metadata.stream.LogicalLevelTechnique; import org.maplibre.mlt.metadata.stream.PhysicalLevelTechnique; import org.maplibre.mlt.metadata.stream.PhysicalStreamType; import org.maplibre.mlt.metadata.stream.StreamMetadata; public class FloatEncoder { private FloatEncoder() {} public static ArrayList encodeFloatStream(List values) throws IOException { // TODO: add encodings -> RLE, Dictionary, PDE final float[] floatArray = new float[values.size()]; for (int i = 0; i < values.size(); i++) { floatArray[i] = values.get(i); } final var encodedValueStream = EncodingUtils.encodeFloatsLE(floatArray); final var valuesMetadata = new StreamMetadata( PhysicalStreamType.DATA, null, LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, PhysicalLevelTechnique.NONE, values.size(), encodedValueStream.length) .encode(); valuesMetadata.add(encodedValueStream); return valuesMetadata; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/GeometryEncoder.java ================================================ package org.maplibre.mlt.converter.encodings; import static org.maplibre.mlt.converter.encodings.IntegerEncoder.encodeFastPfor; import static org.maplibre.mlt.converter.encodings.IntegerEncoder.encodeVarint; import jakarta.annotation.Nullable; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.IntStream; import java.util.stream.Stream; import java.util.stream.StreamSupport; import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.NotNull; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.LinearRing; import org.locationtech.jts.geom.MultiLineString; import org.locationtech.jts.geom.MultiPoint; import org.locationtech.jts.geom.MultiPolygon; import org.locationtech.jts.geom.Point; import org.locationtech.jts.geom.Polygon; import org.maplibre.mlt.converter.ConversionConfig.IntegerEncodingOption; import org.maplibre.mlt.converter.geometry.GeometryType; import org.maplibre.mlt.converter.geometry.GeometryUtils; import org.maplibre.mlt.converter.geometry.HilbertCurve; import org.maplibre.mlt.converter.geometry.SpaceFillingCurve; import org.maplibre.mlt.converter.geometry.Vertex; import org.maplibre.mlt.converter.geometry.ZOrderCurve; import org.maplibre.mlt.converter.tessellation.TessellationUtils; import org.maplibre.mlt.metadata.stream.DictionaryType; import org.maplibre.mlt.metadata.stream.LengthType; import org.maplibre.mlt.metadata.stream.LogicalLevelTechnique; import org.maplibre.mlt.metadata.stream.LogicalStreamType; import org.maplibre.mlt.metadata.stream.OffsetType; import org.maplibre.mlt.metadata.stream.PhysicalLevelTechnique; import org.maplibre.mlt.metadata.stream.PhysicalStreamType; import org.maplibre.mlt.metadata.stream.StreamMetadata; import org.maplibre.mlt.util.ExceptionUtil; import org.maplibre.mlt.util.OptionalUtil; public class GeometryEncoder { public record EncodedGeometryColumn( int numStreams, ArrayList encodedValues, int maxVertexValue, boolean geometryColumnSorted) {} public record SortSettings(boolean isSortable, List featureIds) {} private GeometryEncoder() {} public static EncodedGeometryColumn encodeGeometryColumn( List geometries, PhysicalLevelTechnique physicalLevelTechnique, SortSettings sortSettings, boolean enableMortonEncoding, boolean enableTessellation, boolean encodePolygonOutlines, @Nullable URI tessellateSource, @NotNull IntegerEncodingOption encodingOption) throws IOException { final var geometryTypes = new ArrayList(); final var numGeometries = new ArrayList(); final var numParts = new ArrayList(); final var numRings = new ArrayList(); final var numTriangles = enableTessellation ? new ArrayList() : null; final var indexBuffer = enableTessellation ? new ArrayList() : null; final var vertexBuffer = new ArrayList(); prepareGeometry( geometries, numGeometries, geometryTypes, vertexBuffer, numParts, numRings, numTriangles, indexBuffer, tessellateSource); if (vertexBuffer.isEmpty()) { throw new IllegalArgumentException("The geometry column contains no vertices"); } /* Test if Plain, Vertex Dictionary or Morton Encoded Vertex Dictionary is the most efficient * -> Plain -> convert VertexBuffer with Delta Encoding and specified Physical Level Technique * -> Dictionary -> convert VertexOffsets with IntegerEncoder and VertexBuffer with Delta Encoding and specified Physical Level Technique * -> Morton Encoded Dictionary -> convert VertexOffsets with Integer Encoder and VertexBuffer with IntegerEncoder * */ final var vertexLimits = getVertexLimits(vertexBuffer); final var tryHilbertEncoding = HilbertCurve.isRangeSupported(vertexLimits.min, vertexLimits.max); final var tryMortonEncoding = enableMortonEncoding && ZOrderCurve.isRangeSupported(vertexLimits.min, vertexLimits.max); final Optional hilbertCurve = tryHilbertEncoding ? Optional.of(new HilbertCurve(vertexLimits.min, vertexLimits.max)) : Optional.empty(); final Optional zOrderCurve = tryMortonEncoding ? Optional.of(new ZOrderCurve(vertexLimits.min, vertexLimits.max)) : Optional.empty(); // TODO: if the ratio is lower than 2 dictionary encoding has not to be considered? final var vertexDictionary = hilbertCurve.map(c -> addVerticesToDictionary(vertexBuffer, c)); final var vertexDictionaryHilbertIndexes = vertexDictionary.map(Pair::getLeft); final var vertexDictionaryHilbertMap = vertexDictionaryHilbertIndexes.map(GeometryEncoder::reverseMap); final var vertexDictionaryVertices = vertexDictionary.map(Pair::getRight); final var vertexDictionaryOffsets = hilbertCurve.flatMap( c -> vertexDictionaryHilbertMap.map(m -> getVertexOffsets(vertexBuffer, m::get, c))); final var zigZagDeltaVertexDictionary = vertexDictionaryVertices.map(GeometryEncoder::zigZagDeltaEncodeVertices); final var mortonEncodedDictionary = zOrderCurve.map(c -> addVerticesToMortonDictionary(vertexBuffer, c)); final var mortonEncodedDictionaryOffsets = zOrderCurve.flatMap( c -> mortonEncodedDictionary.map( d -> getVertexOffsets(vertexBuffer, reverseMap(d)::get, c))); // TODO: refactor this simple approach to also work with mixed geometries var geometryColumnSorted = false; if (sortSettings.isSortable && numGeometries.isEmpty() && numRings.isEmpty()) { if (numParts.size() == sortSettings.featureIds.size()) { /* Currently the VertexOffsets are only sorted if all geometries in the geometry column are of type LineString */ if (mortonEncodedDictionaryOffsets.isPresent()) { GeometryUtils.sortVertexOffsets( numParts, mortonEncodedDictionaryOffsets.get(), sortSettings.featureIds()); geometryColumnSorted = true; } } else if (numParts.isEmpty() && hilbertCurve.isPresent()) { GeometryUtils.sortPoints(vertexBuffer, hilbertCurve.get(), sortSettings.featureIds); geometryColumnSorted = true; } } final var zigZagDeltaVertexBuffer = zigZagDeltaEncodeVertices(vertexBuffer); final var encodedVertexBufferStream = encodeVertexBuffer(zigZagDeltaVertexBuffer, physicalLevelTechnique); // TODO: All of these are done only to determine which encoding to use, the actual result is // discarded! Additionally, it's only the size of the raw data, not including metadata. // Instead, we should select based on the size of what's actually written. final var plainVertexBufferSize = IntegerEncoder.encodeInt( zigZagDeltaVertexBuffer, physicalLevelTechnique, false, encodingOption) .encodedValues .length; final var encodedMortonEncodedDictionaryOffsetsSize = mortonEncodedDictionaryOffsets.map( o -> IntegerEncoder.encodeInt(o, physicalLevelTechnique, false, encodingOption) .encodedValues .length); final var encodedDictionaryOffsetsSize = vertexDictionaryOffsets.map( o -> IntegerEncoder.encodeInt(o, physicalLevelTechnique, false, encodingOption) .encodedValues .length); final var encodedVertexDictionarySize = zigZagDeltaVertexDictionary.map( d -> IntegerEncoder.encodeInt(d, physicalLevelTechnique, false, encodingOption) .encodedValues .length); final var encodedMortonVertexDictionarySize = mortonEncodedDictionary.map( ExceptionUtil.unchecked( d -> IntegerEncoder.encodeMortonCodes(d, physicalLevelTechnique) .encodedValues .length)); final var dictionaryEncodedSize = encodedDictionaryOffsetsSize.flatMap(a -> encodedVertexDictionarySize.map(b -> a + b)); final var mortonDictionaryEncodedSize = encodedMortonEncodedDictionaryOffsetsSize.flatMap( a -> encodedMortonVertexDictionarySize.map(b -> a + b)); // TODO: end final var result = IntegerEncoder.encodeIntStream( geometryTypes, physicalLevelTechnique, false, PhysicalStreamType.LENGTH, null, encodingOption); var numStreams = 1; /* Currently use pre-tessellation only if all geometries in a FeatureTable are Polygons or MultiPolygons */ if (enableTessellation && containsOnlyPolygons(geometryTypes)) { // TODO: also support Vertex Dictionary and Morton Encoded Vertex Dictionary encoding? numStreams += encodePolygonPretessellationStreams( result, physicalLevelTechnique, encodingOption, numGeometries, numParts, numRings, numTriangles, indexBuffer, encodePolygonOutlines); result.addAll(encodedVertexBufferStream); return new EncodedGeometryColumn( numStreams + 1, result, vertexLimits.max, geometryColumnSorted); } if (appendLengthStream( result, numGeometries, physicalLevelTechnique, LengthType.GEOMETRIES, encodingOption)) { numStreams++; } if (appendLengthStream( result, numParts, physicalLevelTechnique, LengthType.PARTS, encodingOption)) { numStreams++; } if (appendLengthStream( result, numRings, physicalLevelTechnique, LengthType.RINGS, encodingOption)) { numStreams++; } @NotNull final ArrayList selectedVertexStream; @Nullable final int[] selectedVertexOffsets; final var dictBeatsPlain = dictionaryEncodedSize.map(s -> s < plainVertexBufferSize).orElse(false); final var dictBeatsMorton = OptionalUtil.isLessThan(dictionaryEncodedSize, mortonDictionaryEncodedSize); final var mortonBeatsPlain = mortonDictionaryEncodedSize.map(s -> s < plainVertexBufferSize).orElse(false); final var mortonBeatsDict = OptionalUtil.isLessThan(mortonDictionaryEncodedSize, dictionaryEncodedSize); if (dictBeatsPlain && dictBeatsMorton) { selectedVertexOffsets = vertexDictionaryOffsets.get(); selectedVertexStream = encodeVertexBuffer(zigZagDeltaVertexDictionary.get(), physicalLevelTechnique); geometryColumnSorted = false; } else if (mortonBeatsPlain && mortonBeatsDict) { selectedVertexOffsets = mortonEncodedDictionaryOffsets.get(); selectedVertexStream = IntegerEncoder.encodeMortonStream( mortonEncodedDictionary.get(), zOrderCurve.get().numBits(), zOrderCurve.get().coordinateShift(), physicalLevelTechnique); } else { selectedVertexStream = encodedVertexBufferStream; selectedVertexOffsets = null; } if (selectedVertexOffsets != null && selectedVertexOffsets.length > 0) { result.addAll( IntegerEncoder.encodeIntStream( selectedVertexOffsets, physicalLevelTechnique, false, PhysicalStreamType.OFFSET, new LogicalStreamType(OffsetType.VERTEX), encodingOption)); numStreams++; } result.addAll(selectedVertexStream); return new EncodedGeometryColumn( numStreams + 1, result, vertexLimits.max, geometryColumnSorted); } private static record MinMax(T min, T max) {} private static MinMax getVertexLimits(List vertexBuffer) { var minVertexValue = Integer.MAX_VALUE; var maxVertexValue = Integer.MIN_VALUE; for (final var vertex : vertexBuffer) { final var x = vertex.x(); final var y = vertex.y(); if (x < minVertexValue) minVertexValue = x; if (y < minVertexValue) minVertexValue = y; if (x > maxVertexValue) maxVertexValue = x; if (y > maxVertexValue) maxVertexValue = y; } return new MinMax(minVertexValue, maxVertexValue); } /// Break geometry down into encodable components /// Optional tessellation overload private static void prepareGeometry( List geometries, ArrayList numGeometries, ArrayList geometryTypes, ArrayList vertexBuffer, ArrayList numParts, ArrayList numRings, ArrayList numTriangles, ArrayList indexBuffer, @Nullable URI tessellateSource) { final var containsPolygon = containsPolygon(geometries); final var tessellate = (numTriangles != null && indexBuffer != null); for (var geometry : geometries) { switch (geometry) { case Point point -> { geometryTypes.add(GeometryType.POINT.ordinal()); vertexBuffer.add(new Vertex((int) point.getX(), (int) point.getY())); } case LineString lineString -> { // TODO: verify if part of a MultiPolygon or Polygon geometry add then to numRings? geometryTypes.add(GeometryType.LINESTRING.ordinal()); final var numVertices = lineString.getCoordinates().length; addLineString(containsPolygon, numVertices, numParts, numRings); vertexBuffer.addAll(flatLineString(lineString)); } case Polygon polygon -> { geometryTypes.add(GeometryType.POLYGON.ordinal()); flatPolygon(polygon, vertexBuffer, numParts, numRings); if (tessellate) { final var tessellatedPolygon = TessellationUtils.tessellatePolygon(polygon, 0, tessellateSource); numTriangles.add(tessellatedPolygon.numTriangles()); indexBuffer.addAll(tessellatedPolygon.indexBuffer()); } } case MultiLineString multiLineString -> { // TODO: verify if part of a MultiPolygon or Polygon geometry add then to numRings? geometryTypes.add(GeometryType.MULTILINESTRING.ordinal()); final var numLineStrings = multiLineString.getNumGeometries(); numGeometries.add(numLineStrings); for (var i = 0; i < numLineStrings; i++) { final var lineString = (LineString) multiLineString.getGeometryN(i); final var numVertices = lineString.getCoordinates().length; addLineString(containsPolygon, numVertices, numParts, numRings); vertexBuffer.addAll(flatLineString(lineString)); } } case MultiPolygon multiPolygon -> { geometryTypes.add(GeometryType.MULTIPOLYGON.ordinal()); final var numPolygons = multiPolygon.getNumGeometries(); numGeometries.add(numPolygons); for (var i = 0; i < numPolygons; i++) { final var polygon = (Polygon) multiPolygon.getGeometryN(i); flatPolygon(polygon, vertexBuffer, numParts, numRings); } // TODO: use also a vertex dictionary encoding for MultiPolygon geometries if (tessellate) { final var tessellatedPolygon = TessellationUtils.tessellateMultiPolygon(multiPolygon, tessellateSource); numTriangles.add(tessellatedPolygon.numTriangles()); indexBuffer.addAll(tessellatedPolygon.indexBuffer()); } } case MultiPoint multiPoint -> { geometryTypes.add(GeometryType.MULTIPOINT.ordinal()); final var numPoints = multiPoint.getNumGeometries(); numGeometries.add(numPoints); for (var i = 0; i < numPoints; i++) { final var point = (Point) multiPoint.getGeometryN(i); vertexBuffer.add(new Vertex((int) point.getX(), (int) point.getY())); } } default -> throw new IllegalArgumentException( "Specified geometry type is not (yet) supported: " + geometry.getGeometryType()); } } } private static int encodePolygonPretessellationStreams( final ArrayList result, PhysicalLevelTechnique physicalLevelTechnique, @NotNull IntegerEncodingOption encodingOption, ArrayList numGeometries, ArrayList numParts, ArrayList numRings, ArrayList numTriangles, ArrayList indexBuffer, boolean withOutlines) throws IOException { if (withOutlines) { Objects.requireNonNull(numGeometries); Objects.requireNonNull(numParts); Objects.requireNonNull(numRings); } int numStreams = 1; // TODO: Don't write empty streams final boolean forceEmpty = true; if (withOutlines) { if (appendLengthStream( result, numGeometries, physicalLevelTechnique, LengthType.GEOMETRIES, encodingOption, forceEmpty)) { numStreams++; } if (appendLengthStream( result, numParts, physicalLevelTechnique, LengthType.PARTS, encodingOption, forceEmpty)) { numStreams++; } if (appendLengthStream( result, numRings, physicalLevelTechnique, LengthType.RINGS, encodingOption, forceEmpty)) { numStreams++; } } if (appendLengthStream( result, numTriangles, physicalLevelTechnique, LengthType.TRIANGLES, encodingOption)) { numStreams++; } result.addAll( IntegerEncoder.encodeIntStream( indexBuffer, physicalLevelTechnique, false, PhysicalStreamType.OFFSET, new LogicalStreamType(OffsetType.INDEX), encodingOption)); return numStreams; } private static boolean appendLengthStream( @NotNull ArrayList result, @Nullable List values, @NotNull PhysicalLevelTechnique physicalLevelTechnique, @NotNull LengthType lengthType, @NotNull IntegerEncodingOption encodingOption) throws IOException { return appendLengthStream( result, values, physicalLevelTechnique, lengthType, encodingOption, false); } private static boolean appendLengthStream( @NotNull ArrayList result, @Nullable List values, @NotNull PhysicalLevelTechnique physicalLevelTechnique, @NotNull LengthType lengthType, @NotNull IntegerEncodingOption encodingOption, boolean forceWriteEmptyStream) throws IOException { if (values != null && !values.isEmpty() || forceWriteEmptyStream) { result.addAll( IntegerEncoder.encodeIntStream( (values != null) ? values : List.of(), physicalLevelTechnique, false, PhysicalStreamType.LENGTH, new LogicalStreamType(lengthType), encodingOption)); return true; } return false; } private static boolean containsPolygon(List geometries) { return geometries.stream() .map(Geometry::getGeometryType) .anyMatch( t -> t.equals(Geometry.TYPENAME_MULTIPOLYGON) || t.equals(Geometry.TYPENAME_POLYGON)); } public static boolean containsOnlyPolygons(List geometryTypes) { return geometryTypes.stream() .allMatch( geometryType -> geometryType == GeometryType.POLYGON.ordinal() || geometryType == GeometryType.MULTIPOLYGON.ordinal()); } private static void addLineString( boolean containsPolygon, int numVertices, List numParts, List numRings) { /* Depending on the max geometry type in the column add to the numRings or numParts stream */ if (containsPolygon) { numRings.add(numVertices); } else { numParts.add(numVertices); } } public static int[] zigZagDeltaEncodeVertices(@NotNull final Collection vertices) { return zigZagDeltaEncodeVertices(vertices.stream(), vertices.size()); } public static int[] zigZagDeltaEncodeVertices(@NotNull final Vertex[] vertices) { return zigZagDeltaEncodeVertices( StreamSupport.stream(Arrays.spliterator(vertices), false), vertices.length); } private static int[] zigZagDeltaEncodeVertices( @NotNull final Stream vertices, final int size) { int prevX = 0; int prevY = 0; int j = 0; final var deltaValues = new int[size * 2]; for (var iter = vertices.iterator(); iter.hasNext(); ) { final var vertex = iter.next(); final var x = vertex.x(); final var y = vertex.y(); deltaValues[j++] = EncodingUtils.encodeZigZag(x - prevX); deltaValues[j++] = EncodingUtils.encodeZigZag(y - prevY); prevX = x; prevY = y; } return deltaValues; } private static int[] getVertexOffsets( List vertexBuffer, Function vertexOffsetSupplier, SpaceFillingCurve curve) { int[] result = new int[vertexBuffer.size()]; int i = 0; for (var vertex : vertexBuffer) { result[i++] = vertexOffsetSupplier.apply(curve.encode(vertex)); } return result; } private static Map reverseMap(IntStream mortonEncodedDictionary, int size) { Map morton = HashMap.newHashMap(size); int i = 0; for (var iter = mortonEncodedDictionary.iterator(); iter.hasNext(); ) { morton.put(iter.nextInt(), i++); } return morton; } private static Map reverseMap(Collection mortonEncodedDictionary) { return reverseMap( mortonEncodedDictionary.stream().mapToInt(Integer::intValue), mortonEncodedDictionary.size()); } private static Map reverseMap(int[] mortonEncodedDictionary) { return reverseMap(IntStream.of(mortonEncodedDictionary), mortonEncodedDictionary.length); } /// An entry in the vertex dictionary, used for sorting and filtering duplicates record Indexed(int hilbert, int index) implements Comparable { @Override public int compareTo(@NotNull GeometryEncoder.Indexed o) { return Integer.compare(hilbert, o.hilbert); } } /// A predicate for filtering consecutive duplicates from streams of `Indexed` private static Predicate distinctByHilbertId() { return new Predicate() { private boolean first = true; private int lastSeen; @Override public boolean test(Indexed indexed) { if (first || indexed.hilbert != lastSeen) { lastSeen = indexed.hilbert; first = false; return true; } return false; } }; } private static Pair addVerticesToDictionary( @NotNull final ArrayList vertices, @NotNull final HilbertCurve hilbertCurve) { // 1. Convert to (hilbertId, vertex) pairs // 2. Sort by hilbertId // 3. Filter consecutive duplicates // 4. Convert back to separate arrays for hilbertIds and vertices // Can we do this without materializing the intermediate list? final var vertexDictionary = IntStream.range(0, vertices.size()) .mapToObj(i -> new Indexed(hilbertCurve.encode(vertices.get(i)), i)) .sorted(Comparator.naturalOrder()) // TODO: we currently don't filter duplicates in the vertex dictionary! // .filter(distinctByHilbertId()) .toList(); return Pair.of( vertexDictionary.stream().mapToInt(Indexed::hilbert).toArray(), vertexDictionary.stream().map(i -> vertices.get(i.index)).toArray(Vertex[]::new)); } private static int[] addVerticesToMortonDictionary( @NotNull final Collection vertices, @NotNull final ZOrderCurve zOrderCurve) { return vertices.stream().mapToInt(zOrderCurve::encode).sorted().toArray(); } private static List flatLineString(LineString lineString) { return Arrays.stream(lineString.getCoordinates()) .map(v -> new Vertex((int) v.x, (int) v.y)) .toList(); } private static LineString ringToLineString(LinearRing ring, GeometryFactory factory) { return factory.createLineString( Arrays.copyOf(ring.getCoordinates(), ring.getCoordinates().length - 1)); } private static void flatPolygon( Polygon polygon, ArrayList vertices, List partSize, List ringSize) { final var factory = new GeometryFactory(); // 1 for the outline, 1 for each interior ring partSize.add(1 + polygon.getNumInteriorRing()); final var exteriorRing = polygon.getExteriorRing(); // If the ring isn't closed, our assumptions about the number of vertices will be incorrect. assert (exteriorRing.isClosed()); final var shell = ringToLineString(exteriorRing, factory); vertices.addAll(flatLineString(shell)); ringSize.add(shell.getNumPoints()); for (var i = 0; i < polygon.getNumInteriorRing(); i++) { final var interiorRing = polygon.getInteriorRingN(i); assert (interiorRing.isClosed()); final var ring = ringToLineString(interiorRing, factory); vertices.addAll(flatLineString(ring)); ringSize.add(ring.getNumPoints()); } } /** * Encodes the StreamMetadata and applies the specified physical level technique to the values. */ private static ArrayList encodeVertexBuffer( int[] values, PhysicalLevelTechnique physicalLevelTechnique) throws IOException { final var encodedValues = physicalLevelTechnique == PhysicalLevelTechnique.FAST_PFOR ? encodeFastPfor(values, false) : encodeVarint(values, false); final var result = new StreamMetadata( PhysicalStreamType.DATA, new LogicalStreamType(DictionaryType.VERTEX), LogicalLevelTechnique.COMPONENTWISE_DELTA, LogicalLevelTechnique.NONE, physicalLevelTechnique, values.length, encodedValues.length) .encode(); result.add(encodedValues); return result; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/IntegerEncoder.java ================================================ package org.maplibre.mlt.converter.encodings; import com.google.common.collect.Lists; import com.google.common.primitives.Ints; import com.google.common.primitives.Longs; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.BiFunction; import org.jetbrains.annotations.NotNull; import org.maplibre.mlt.converter.CollectionUtils; import org.maplibre.mlt.converter.ConversionConfig.IntegerEncodingOption; import org.maplibre.mlt.metadata.stream.DictionaryType; import org.maplibre.mlt.metadata.stream.LogicalLevelTechnique; import org.maplibre.mlt.metadata.stream.LogicalStreamType; import org.maplibre.mlt.metadata.stream.MortonEncodedStreamMetadata; import org.maplibre.mlt.metadata.stream.PhysicalLevelTechnique; import org.maplibre.mlt.metadata.stream.PhysicalStreamType; import org.maplibre.mlt.metadata.stream.RleEncodedStreamMetadata; import org.maplibre.mlt.metadata.stream.StreamMetadata; /* * TODO: Add sampling strategy for encoding selection * -> Inspired by BTRBlock sampling strategy: * - take about 1% of all data as samples * - divide into ? blocks? * -> https://github.com/maxi-k/btrblocks/blob/c954ffd31f0873003dbc26bf1676ac460d7a3b05/btrblocks/scheme/double/RLE.cpp#L17 * */ public class IntegerEncoder { public static class IntegerEncodingResult { public LogicalLevelTechnique logicalLevelTechnique1; public LogicalLevelTechnique logicalLevelTechnique2; public byte[] encodedValues; /* If rle or delta-rle encoding is used, otherwise can be ignored */ public int numRuns; public int physicalLevelEncodedValuesLength; } enum LogicalLevelIntegerTechnique { PLAIN, DELTA, RLE, DELTA_RLE } private IntegerEncoder() {} public static ArrayList encodeMortonStream( int[] values, int numBits, int coordinateShift, PhysicalLevelTechnique physicalLevelTechnique) throws IOException { final var encodedValueStream = encodeMortonCodes(values, physicalLevelTechnique); final var valuesMetadata = new MortonEncodedStreamMetadata( PhysicalStreamType.DATA, new LogicalStreamType(DictionaryType.MORTON), encodedValueStream.logicalLevelTechnique1, encodedValueStream.logicalLevelTechnique2, physicalLevelTechnique, encodedValueStream.physicalLevelEncodedValuesLength, encodedValueStream.encodedValues.length, numBits, coordinateShift); final var result = valuesMetadata.encode(); result.add(encodedValueStream.encodedValues); return result; } // Encodes integer stream with AUTO encoding option (backward compatibility). public static ArrayList encodeIntStream( List values, PhysicalLevelTechnique physicalLevelTechnique, boolean isSigned, PhysicalStreamType streamType, LogicalStreamType logicalStreamType) throws IOException { return encodeIntStream( CollectionUtils.unboxInts(values), physicalLevelTechnique, isSigned, streamType, logicalStreamType); } // Encodes integer stream with AUTO encoding option (backward compatibility). public static ArrayList encodeIntStream( int[] values, PhysicalLevelTechnique physicalLevelTechnique, boolean isSigned, PhysicalStreamType streamType, LogicalStreamType logicalStreamType) throws IOException { return encodeIntStream( values, physicalLevelTechnique, isSigned, streamType, logicalStreamType, IntegerEncodingOption.AUTO); } public static ArrayList encodeIntStream( List values, PhysicalLevelTechnique physicalLevelTechnique, boolean isSigned, PhysicalStreamType streamType, LogicalStreamType logicalStreamType, @NotNull IntegerEncodingOption encodingOption) throws IOException { return encodeIntStream( CollectionUtils.unboxInts(values), physicalLevelTechnique, isSigned, streamType, logicalStreamType, encodingOption); } public static ArrayList encodeIntStream( int[] values, PhysicalLevelTechnique physicalLevelTechnique, boolean isSigned, PhysicalStreamType streamType, LogicalStreamType logicalStreamType, @NotNull IntegerEncodingOption encodingOption) throws IOException { final var encodedValueStream = IntegerEncoder.encodeInt(values, physicalLevelTechnique, isSigned, encodingOption); // TODO: refactor -> also allow the use of none null suppression techniques final var streamMetadata = (encodedValueStream.logicalLevelTechnique1 == LogicalLevelTechnique.RLE || encodedValueStream.logicalLevelTechnique2 == LogicalLevelTechnique.RLE) ? new RleEncodedStreamMetadata( streamType, logicalStreamType, encodedValueStream.logicalLevelTechnique1, encodedValueStream.logicalLevelTechnique2, physicalLevelTechnique, encodedValueStream.physicalLevelEncodedValuesLength, encodedValueStream.encodedValues.length, encodedValueStream.numRuns, values.length) : new StreamMetadata( streamType, logicalStreamType, encodedValueStream.logicalLevelTechnique1, encodedValueStream.logicalLevelTechnique2, physicalLevelTechnique, encodedValueStream.physicalLevelEncodedValuesLength, encodedValueStream.encodedValues.length); final var result = streamMetadata.encode(); result.add(encodedValueStream.encodedValues); return result; } // Encodes long stream with AUTO encoding option (backward compatibility). public static ArrayList encodeLongStream( long[] values, boolean isSigned, PhysicalStreamType streamType, LogicalStreamType logicalStreamType) throws IOException { return encodeLongStream( values, isSigned, streamType, logicalStreamType, IntegerEncodingOption.AUTO); } public static ArrayList encodeLongStream( long[] values, boolean isSigned, PhysicalStreamType streamType, LogicalStreamType logicalStreamType, @NotNull IntegerEncodingOption encodingOption) throws IOException { final var encodedValueStream = IntegerEncoder.encodeLong(values, isSigned, encodingOption); /* Currently FastPfor is only supported with 32 bit so for long we always have to fallback to Varint encoding */ final var streamMetadata = (encodedValueStream.logicalLevelTechnique1 == LogicalLevelTechnique.RLE || encodedValueStream.logicalLevelTechnique2 == LogicalLevelTechnique.RLE) ? new RleEncodedStreamMetadata( streamType, logicalStreamType, encodedValueStream.logicalLevelTechnique1, encodedValueStream.logicalLevelTechnique2, PhysicalLevelTechnique.VARINT, encodedValueStream.physicalLevelEncodedValuesLength, encodedValueStream.encodedValues.length, encodedValueStream.numRuns, values.length) : new StreamMetadata( streamType, logicalStreamType, encodedValueStream.logicalLevelTechnique1, encodedValueStream.logicalLevelTechnique2, PhysicalLevelTechnique.VARINT, encodedValueStream.physicalLevelEncodedValuesLength, encodedValueStream.encodedValues.length); final var result = streamMetadata.encode(); result.add(encodedValueStream.encodedValues); return result; } // TODO: make dependent on specified LogicalLevelTechnique public static IntegerEncodingResult encodeMortonCodes( int[] values, PhysicalLevelTechnique physicalLevelTechnique) throws IOException { var previousValue = 0; int[] deltaValues = new int[values.length]; for (var i = 0; i < values.length; i++) { var value = values[i]; var delta = value - previousValue; deltaValues[i] = delta; previousValue = value; } var encodedValues = physicalLevelTechnique == PhysicalLevelTechnique.FAST_PFOR ? encodeFastPfor(deltaValues, false) : EncodingUtils.encodeVarints(deltaValues, false, false); var result = new IntegerEncodingResult(); result.logicalLevelTechnique1 = LogicalLevelTechnique.MORTON; result.logicalLevelTechnique2 = LogicalLevelTechnique.DELTA; result.physicalLevelEncodedValuesLength = values.length; result.numRuns = 0; result.encodedValues = encodedValues; return result; } // Encodes integers with AUTO encoding option (backward compatibility). public static IntegerEncodingResult encodeInt( int[] values, PhysicalLevelTechnique physicalLevelTechnique, boolean isSigned) { return encodeInt(values, physicalLevelTechnique, isSigned, IntegerEncodingOption.AUTO); } /* * Integers are encoded based on the two lightweight compression techniques delta and rle as well * as a combination of both schemes called delta-rle. * */ public static IntegerEncodingResult encodeInt( int[] values, PhysicalLevelTechnique physicalLevelTechnique, boolean isSigned, @NotNull IntegerEncodingOption encodingOption) { var previousValue = 0; var previousDelta = 0; var runs = 1; var deltaRuns = 1; var deltaValues = new int[values.length]; for (int i = 0; i < values.length; i++) { int value = values[i]; var delta = value - previousValue; deltaValues[i] = delta; if (value != previousValue && i != 0) { runs++; } if (delta != previousDelta && i != 0) { deltaRuns++; } previousValue = value; previousDelta = delta; } BiFunction encoder = physicalLevelTechnique == PhysicalLevelTechnique.FAST_PFOR ? IntegerEncoder::encodeFastPfor : (v, s) -> { try { return EncodingUtils.encodeVarints(v, s, false); } catch (IOException e) { throw new RuntimeException(e); } }; // Early return for forced PLAIN encoding if (encodingOption == IntegerEncodingOption.PLAIN) { var result = new IntegerEncodingResult(); result.encodedValues = encoder.apply(values, isSigned); result.physicalLevelEncodedValuesLength = values.length; result.logicalLevelTechnique1 = LogicalLevelTechnique.NONE; result.logicalLevelTechnique2 = LogicalLevelTechnique.NONE; return result; } // Early return for forced DELTA encoding if (encodingOption == IntegerEncodingOption.DELTA) { var result = new IntegerEncodingResult(); result.encodedValues = encoder.apply(deltaValues, true); result.physicalLevelEncodedValuesLength = values.length; result.logicalLevelTechnique1 = LogicalLevelTechnique.DELTA; result.logicalLevelTechnique2 = LogicalLevelTechnique.NONE; return result; } var plainEncodedValues = encoder.apply(values, isSigned); var deltaEncodedValues = encoder.apply(deltaValues, true); var encodedValues = Lists.newArrayList(plainEncodedValues, deltaEncodedValues); byte[] rleEncodedValues = null; byte[] deltaRleEncodedValues = null; var rlePhysicalLevelEncodedValuesLength = 0; var deltaRlePhysicalLevelEncodedValuesLength = 0; /* Use selection logic from BTR Blocks -> https://github.com/maxi-k/btrblocks/blob/c954ffd31f0873003dbc26bf1676ac460d7a3b05/btrblocks/scheme/double/RLE.cpp#L17 */ /* * if there are ony a view values (e.g. 4 times 1) rle only produces the same size then other * encodings. Since we want to force that all const streams use RLE encoding we use this current * workaround */ var isConstStream = false; if (values.length / runs >= 2 && (encodingOption == IntegerEncodingOption.AUTO || encodingOption == IntegerEncodingOption.RLE)) { var rleValues = EncodingUtils.encodeRle(values); rlePhysicalLevelEncodedValuesLength = rleValues.getLeft().length + rleValues.getRight().length; rleEncodedValues = encoder.apply( Ints.concat( rleValues.getLeft(), isSigned ? EncodingUtils.encodeZigZag(rleValues.getRight()) : rleValues.getRight()), false); isConstStream = rleValues.getLeft().length == 1; // Early return for forced RLE encoding if (encodingOption == IntegerEncodingOption.RLE) { var result = new IntegerEncodingResult(); result.encodedValues = rleEncodedValues; result.physicalLevelEncodedValuesLength = rlePhysicalLevelEncodedValuesLength; result.numRuns = runs; result.logicalLevelTechnique1 = LogicalLevelTechnique.RLE; result.logicalLevelTechnique2 = LogicalLevelTechnique.NONE; return result; } } if (deltaValues.length / deltaRuns >= 2) { // TODO: get rid of conversion var deltaRleValues = EncodingUtils.encodeRle(deltaValues); deltaRlePhysicalLevelEncodedValuesLength = deltaRleValues.getLeft().length + deltaRleValues.getRight().length; var zigZagDelta = EncodingUtils.encodeZigZag(deltaRleValues.getRight()); // TODO: encode runs and length separate? deltaRleEncodedValues = encoder.apply(Ints.concat(deltaRleValues.getLeft(), zigZagDelta), false); // Early return for forced DELTA_RLE encoding if (encodingOption == IntegerEncodingOption.DELTA_RLE) { var result = new IntegerEncodingResult(); result.encodedValues = deltaRleEncodedValues; result.physicalLevelEncodedValuesLength = deltaRlePhysicalLevelEncodedValuesLength; result.numRuns = deltaRuns; result.logicalLevelTechnique1 = LogicalLevelTechnique.DELTA; result.logicalLevelTechnique2 = LogicalLevelTechnique.RLE; return result; } } encodedValues.add(rleEncodedValues); encodedValues.add(deltaRleEncodedValues); // TODO: refactor -> find proper solution final var encodedValuesSizes = encodedValues.stream().map(v -> v == null ? Integer.MAX_VALUE : v.length).toList(); final var index = isConstStream ? LogicalLevelIntegerTechnique.RLE.ordinal() : encodedValuesSizes.indexOf(Collections.min(encodedValuesSizes)); final var encoding = LogicalLevelIntegerTechnique.values()[index]; final var result = new IntegerEncodingResult(); result.encodedValues = encodedValues.get(index); result.physicalLevelEncodedValuesLength = values.length; if (encoding == LogicalLevelIntegerTechnique.RLE || isConstStream) { result.numRuns = runs; result.physicalLevelEncodedValuesLength = rlePhysicalLevelEncodedValuesLength; result.logicalLevelTechnique1 = LogicalLevelTechnique.RLE; result.logicalLevelTechnique2 = LogicalLevelTechnique.NONE; } else if (encoding == LogicalLevelIntegerTechnique.DELTA_RLE) { result.numRuns = deltaRuns; result.physicalLevelEncodedValuesLength = deltaRlePhysicalLevelEncodedValuesLength; result.logicalLevelTechnique1 = LogicalLevelTechnique.DELTA; result.logicalLevelTechnique2 = LogicalLevelTechnique.RLE; } else if (encoding == LogicalLevelIntegerTechnique.DELTA) { result.logicalLevelTechnique1 = LogicalLevelTechnique.DELTA; result.logicalLevelTechnique2 = LogicalLevelTechnique.NONE; } else { result.logicalLevelTechnique1 = LogicalLevelTechnique.NONE; result.logicalLevelTechnique2 = LogicalLevelTechnique.NONE; } return result; } // Encodes long values with AUTO encoding option (backward compatibility). public static IntegerEncodingResult encodeLong(long[] values, boolean isSigned) { return encodeLong(values, isSigned, IntegerEncodingOption.AUTO); } public static IntegerEncodingResult encodeLong( long[] values, boolean isSigned, @NotNull IntegerEncodingOption encodingOption) { var previousValue = 0L; var previousDelta = 0L; var runs = 1; var deltaRuns = 1; var deltaValues = new long[values.length]; for (var i = 0; i < values.length; i++) { var value = values[i]; var delta = value - previousValue; deltaValues[i] = delta; if (value != previousValue && i != 0) { runs++; } if (delta != previousDelta && i != 0) { deltaRuns++; } previousValue = value; previousDelta = delta; } BiFunction encoder = (v, s) -> { try { return EncodingUtils.encodeLongVarints(v, s, false); } catch (IOException e) { throw new RuntimeException(e); } }; if (encodingOption == IntegerEncodingOption.PLAIN) { final var result = new IntegerEncodingResult(); result.encodedValues = encoder.apply(values, isSigned); result.physicalLevelEncodedValuesLength = values.length; result.logicalLevelTechnique1 = LogicalLevelTechnique.NONE; result.logicalLevelTechnique2 = LogicalLevelTechnique.NONE; return result; } if (encodingOption == IntegerEncodingOption.DELTA) { final var result = new IntegerEncodingResult(); result.encodedValues = encoder.apply(deltaValues, true); result.physicalLevelEncodedValuesLength = values.length; result.logicalLevelTechnique1 = LogicalLevelTechnique.DELTA; result.logicalLevelTechnique2 = LogicalLevelTechnique.NONE; return result; } final var plainEncodedValues = encoder.apply(values, isSigned); final var deltaEncodedValues = encoder.apply(deltaValues, true); final var encodedValues = Lists.newArrayList(plainEncodedValues, deltaEncodedValues); byte[] rleEncodedValues = null; byte[] deltaRleEncodedValues = null; var rlePhysicalLevelEncodedValuesLength = 0; var deltaRlePhysicalLevelEncodedValuesLength = 0; var isConstStream = false; /* Use selection logic from BTR Blocks -> https://github.com/maxi-k/btrblocks/blob/c954ffd31f0873003dbc26bf1676ac460d7a3b05/btrblocks/scheme/double/RLE.cpp#L17 */ if (values.length / runs >= 2 && (encodingOption == IntegerEncodingOption.AUTO || encodingOption == IntegerEncodingOption.RLE)) { // TODO: get rid of conversion final var rleValues = EncodingUtils.encodeRle(values); rlePhysicalLevelEncodedValuesLength = rleValues.getLeft().length + rleValues.getRight().length; rleEncodedValues = encoder.apply( Longs.concat( rleValues.getLeft(), isSigned ? EncodingUtils.encodeZigZag(rleValues.getRight()) : rleValues.getRight()), false); isConstStream = rleValues.getLeft().length == 1; if (encodingOption == IntegerEncodingOption.RLE) { final var result = new IntegerEncodingResult(); result.encodedValues = rleEncodedValues; result.physicalLevelEncodedValuesLength = rlePhysicalLevelEncodedValuesLength; result.numRuns = runs; result.logicalLevelTechnique1 = LogicalLevelTechnique.RLE; result.logicalLevelTechnique2 = LogicalLevelTechnique.NONE; return result; } } if (deltaValues.length / deltaRuns >= 2) { // TODO: get rid of conversion final var deltaRleValues = EncodingUtils.encodeRle(deltaValues); deltaRlePhysicalLevelEncodedValuesLength = deltaRleValues.getLeft().length + deltaRleValues.getRight().length; final var zigZagDelta = EncodingUtils.encodeZigZag(deltaRleValues.getRight()); // TODO: encode runs and length separate? deltaRleEncodedValues = encoder.apply(Longs.concat(deltaRleValues.getLeft(), zigZagDelta), false); if (encodingOption == IntegerEncodingOption.DELTA_RLE) { final var result = new IntegerEncodingResult(); result.encodedValues = deltaRleEncodedValues; result.physicalLevelEncodedValuesLength = deltaRlePhysicalLevelEncodedValuesLength; result.numRuns = deltaRuns; result.logicalLevelTechnique1 = LogicalLevelTechnique.DELTA; result.logicalLevelTechnique2 = LogicalLevelTechnique.RLE; return result; } } encodedValues.add(rleEncodedValues); encodedValues.add(deltaRleEncodedValues); // TODO: refactor -> find proper solution final var encodedValuesSizes = encodedValues.stream().map(v -> v == null ? Integer.MAX_VALUE : v.length).toList(); final var index = isConstStream ? LogicalLevelIntegerTechnique.RLE.ordinal() : encodedValuesSizes.indexOf(Collections.min(encodedValuesSizes)); final var encoding = LogicalLevelIntegerTechnique.values()[index]; final var result = new IntegerEncodingResult(); result.encodedValues = encodedValues.get(index); result.physicalLevelEncodedValuesLength = values.length; if (encoding == LogicalLevelIntegerTechnique.RLE || isConstStream) { result.numRuns = runs; result.physicalLevelEncodedValuesLength = rlePhysicalLevelEncodedValuesLength; result.logicalLevelTechnique1 = LogicalLevelTechnique.RLE; result.logicalLevelTechnique2 = LogicalLevelTechnique.NONE; } else if (encoding == LogicalLevelIntegerTechnique.DELTA_RLE) { result.numRuns = deltaRuns; result.physicalLevelEncodedValuesLength = deltaRlePhysicalLevelEncodedValuesLength; result.logicalLevelTechnique1 = LogicalLevelTechnique.DELTA; result.logicalLevelTechnique2 = LogicalLevelTechnique.RLE; } else if (encoding == LogicalLevelIntegerTechnique.DELTA) { result.logicalLevelTechnique1 = LogicalLevelTechnique.DELTA; result.logicalLevelTechnique2 = LogicalLevelTechnique.NONE; } else { result.logicalLevelTechnique1 = LogicalLevelTechnique.NONE; result.logicalLevelTechnique2 = LogicalLevelTechnique.NONE; } return result; } public static byte[] encodeFastPfor(int[] values, boolean signed) { return EncodingUtils.encodeFastPfor128(values, signed, false); } public static byte[] encodeVarint(int[] values, boolean signed) throws IOException { return EncodingUtils.encodeVarints(values, signed, false); } public static byte[] encodeLongVarint(long[] values, boolean signed) throws IOException { return EncodingUtils.encodeLongVarints(values, signed, false); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/LinearRegression.java ================================================ package org.maplibre.mlt.converter.encodings; import java.util.Arrays; // Source: https://github.com/yhliu918/Learn-to-Compress public class LinearRegression { public static double[] calculateDeltas(double[] indices, double[] actualValues, double[] theta) { var predictions = calculatePredictions(indices, theta); var deltas = new double[actualValues.length]; for (var i = 0; i < actualValues.length; i++) { deltas[i] = Math.abs(predictions[i] - actualValues[i]); } return deltas; } public static double[] calculatePredictions(double[] x, double[] theta) { int m = x.length; double[] predictions = new double[m]; for (int i = 0; i < m; ++i) { predictions[i] = h(x[i], theta); } return predictions; } private static double h(double x, double[] theta) { return theta[0] + theta[1] * x; } public static double computeCost(double[] x, double[] y, double[] theta) { int m = x.length; double[] predictions = calculatePredictions(x, theta); double[] diff = arrayDiff(predictions, y); double[] sqErrors = arrayPow(diff, 2); return (1.0 / (2 * m)) * arraySum(sqErrors); } public static double[] gradientDescent( double[] x, double[] y, double alpha, int iters, double[] J) { int m = x.length; // int m = 1; double[] theta = new double[2]; theta[1] = (y[m - 1] - y[0]) / (x[m - 1] - x[0]); theta[0] = y[0] - theta[1] * x[0]; for (int i = 0; i < iters; ++i) { double[] predictions = calculatePredictions(x, theta); double[] diff = arrayDiff(predictions, y); double[] errorsX1 = diff; double[] errorsX2 = arrayMultiplication(diff, x); theta[0] -= alpha * (1.0 / m) * arraySum(errorsX1); theta[1] -= alpha * (1.0 / m) * arraySum(errorsX2); J[i] = computeCost(x, y, theta); } return theta; } public static double[] arrayDiff(double[] arr1, double[] arr2) { int len = arr1.length; double[] arr = new double[len]; for (int i = 0; i < len; ++i) { arr[i] = arr1[i] - arr2[i]; } return arr; } public static double[] arrayPow(double[] arr, int power) { int len = arr.length; double[] arr2 = new double[len]; for (int i = 0; i < len; ++i) { arr2[i] = Math.pow(arr[i], power); } return arr2; } public static double[] arrayMultiplication(double[] arr1, double[] arr2) { int len = arr1.length; double[] arr = new double[len]; for (int i = 0; i < len; ++i) { arr[i] = arr1[i] * arr2[i]; } return arr; } public static double arraySum(double[] arr) { return Arrays.stream(arr).sum(); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/MltTypeMap.java ================================================ package org.maplibre.mlt.converter.encodings; import jakarta.annotation.Nullable; import java.util.List; import java.util.Optional; import org.maplibre.mlt.metadata.tileset.MltMetadata; public class MltTypeMap { public static class Tag0x01 { /// Produces the unique type encoding for a `Column` or `Field` /// @param physicalScalarType The physical scalar type, if applicable /// @param logicalScalarType The logical scalar type, if applicable /// @param physicalComplexType The physical complex type, if applicable /// @param ignoredLogicalComplexType not currently used /// @param isNullable Whether the column is nullable /// @param hasChildren Whether the column has children (i.e. is a struct) /// @param hasLongIDs Whether the column has long IDs (i.e. 64-bit vs 32-bit) /// @return A unique integer encoding of the column type, if supported by the encoding scheme public static Optional encodeColumnType( @Nullable MltMetadata.ScalarType physicalScalarType, @Nullable MltMetadata.LogicalScalarType logicalScalarType, @Nullable MltMetadata.ComplexType physicalComplexType, @Nullable MltMetadata.LogicalComplexType ignoredLogicalComplexType, boolean isNullable, boolean hasChildren, boolean hasLongIDs) { if (physicalScalarType != null) { if (!hasChildren) { return switch (physicalScalarType) { case BOOLEAN -> Optional.of(isNullable ? 11 : 10); case INT_8 -> Optional.of(isNullable ? 13 : 12); case UINT_8 -> Optional.of(isNullable ? 15 : 14); case INT_32 -> Optional.of(isNullable ? 17 : 16); case UINT_32 -> Optional.of(isNullable ? 19 : 18); case INT_64 -> Optional.of(isNullable ? 21 : 20); case UINT_64 -> Optional.of(isNullable ? 23 : 22); case FLOAT -> Optional.of(isNullable ? 25 : 24); case DOUBLE -> Optional.of(isNullable ? 27 : 26); case STRING -> Optional.of(isNullable ? 29 : 28); default -> Optional.empty(); }; } } else if (logicalScalarType != null) { if (logicalScalarType == MltMetadata.LogicalScalarType.ID) { return Optional.of(isNullable ? (hasLongIDs ? 3 : 1) : (hasLongIDs ? 2 : 0)); } } else if (physicalComplexType != null) { if (physicalComplexType == MltMetadata.ComplexType.GEOMETRY) { if (!isNullable && !hasChildren) { return Optional.of(4); } } else if (physicalComplexType == MltMetadata.ComplexType.STRUCT) { if (!isNullable && hasChildren) { return Optional.of(30); } } } return Optional.empty(); } /// Re-create a `Column` from the unique type code. /// The inverse of `getTag1TypeEncoding`` /// @param typeCode the unique type code encoding the column's type /// @return The equivalent type descriptor public static MltMetadata.FieldType decodeColumnType(int typeCode) { if (10 <= typeCode && typeCode <= 29) { final var isNullable = (typeCode & 1) != 0; return MltMetadata.scalarFieldType(getScalarType(typeCode), isNullable); } else if (0 <= typeCode && typeCode <= 3) { final var isNullable = (typeCode & 1) != 0; final var hasLongId = (typeCode > 1); return MltMetadata.idFieldType(hasLongId, isNullable); } else if (4 == typeCode) { return MltMetadata.geometryFieldType(); } else if (30 == typeCode) { return MltMetadata.structFieldType((List) null); } else { throw new IllegalStateException("Unsupported Type " + typeCode); } } private static MltMetadata.ScalarType getScalarType(int typeCode) { return switch (typeCode) { case 10, 11 -> MltMetadata.ScalarType.BOOLEAN; case 12, 13 -> MltMetadata.ScalarType.INT_8; case 14, 15 -> MltMetadata.ScalarType.UINT_8; case 16, 17 -> MltMetadata.ScalarType.INT_32; case 18, 19 -> MltMetadata.ScalarType.UINT_32; case 20, 21 -> MltMetadata.ScalarType.INT_64; case 22, 23 -> MltMetadata.ScalarType.UINT_64; case 24, 25 -> MltMetadata.ScalarType.FLOAT; case 26, 27 -> MltMetadata.ScalarType.DOUBLE; case 28, 29 -> MltMetadata.ScalarType.STRING; default -> throw new IllegalStateException("Unsupported Type"); }; } public static boolean columnTypeHasName(int typeCode) { return (10 <= typeCode); } public static boolean columnTypeHasChildren(int typeCode) { return (typeCode == 30); } public static boolean isID(MltMetadata.Column column) { return isID(column.field().type()); } public static boolean isID(MltMetadata.FieldType type) { return type.is(MltMetadata.LogicalScalarType.ID); } public static boolean isGeometry(MltMetadata.Column column) { return isGeometry(column.field().type()); } public static boolean isGeometry(MltMetadata.FieldType type) { return type.is(MltMetadata.ComplexType.GEOMETRY); } public static boolean isStruct(MltMetadata.Column column) { return isStruct(column.field().type()); } public static boolean isStruct(MltMetadata.FieldType type) { return type.is(MltMetadata.ComplexType.STRUCT); } public static boolean hasStreamCount(MltMetadata.Column column) { return hasStreamCount(column.field().type()); } public static boolean hasStreamCount(MltMetadata.FieldType type) { if (type.scalarType() != null) { final var scalar = type.scalarType(); if (scalar.physicalType() != null) { switch (scalar.physicalType()) { case BOOLEAN: case INT_8: case UINT_8: case INT_32: case UINT_32: case INT_64: case UINT_64: case FLOAT: case DOUBLE: return false; case STRING: return true; default: } } else if (scalar.logicalType() != null) { if (scalar.logicalType() == MltMetadata.LogicalScalarType.ID) { return false; } } } else if (type.complexType() != null) { final var complex = type.complexType(); if (complex.physicalType() != null) { switch (complex.physicalType()) { case GEOMETRY: case STRUCT: return true; default: } } // Complex/Logical not currently used } assert false; return false; } } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/PropertyEncoder.java ================================================ package org.maplibre.mlt.converter.encodings; import jakarta.annotation.Nullable; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.List; import java.util.Objects; import java.util.SequencedCollection; import java.util.stream.Collectors; import org.jetbrains.annotations.NotNull; import org.maplibre.mlt.converter.CollectionUtils; import org.maplibre.mlt.converter.ColumnMapping; import org.maplibre.mlt.converter.ConversionConfig; import org.maplibre.mlt.data.Feature; import org.maplibre.mlt.data.unsigned.U32; import org.maplibre.mlt.data.unsigned.U64; import org.maplibre.mlt.data.unsigned.U8; import org.maplibre.mlt.metadata.stream.LogicalLevelTechnique; import org.maplibre.mlt.metadata.stream.PhysicalLevelTechnique; import org.maplibre.mlt.metadata.stream.PhysicalStreamType; import org.maplibre.mlt.metadata.stream.StreamMetadata; import org.maplibre.mlt.metadata.tileset.MltMetadata; import org.maplibre.mlt.util.ByteArrayUtil; import org.maplibre.mlt.util.StreamUtil; public class PropertyEncoder { public static ArrayList encodePropertyColumns( SequencedCollection propertyColumns, SequencedCollection features, boolean useFastPFOR, boolean useFSST, boolean coercePropertyValues, @Nullable SequencedCollection columnMappings, @NotNull ConversionConfig.IntegerEncodingOption integerEncodingOption) throws IOException { /* * TODOs: - detect if column is nullable to get rid of the present stream - test boolean rle * against roaring bitmaps and integer encoding for present stream and boolean values - Add * vector type to field metadata */ final var physicalLevelTechnique = useFastPFOR ? PhysicalLevelTechnique.FAST_PFOR : PhysicalLevelTechnique.VARINT; final var estimatedBuffers = propertyColumns.size() * 5; final var featureScopedPropertyColumns = new ArrayList(estimatedBuffers); final var columnMappingsIterator = columnMappings.iterator(); for (var columnMetadata : propertyColumns) { final ArrayList encodedColumn; if (columnMetadata.isScalar()) { encodedColumn = encodeScalarPropertyColumn( features, useFSST, coercePropertyValues, columnMetadata, physicalLevelTechnique, integerEncodingOption); } else if (MltTypeMap.Tag0x01.isStruct(columnMetadata)) { if (!columnMappingsIterator.hasNext()) { throw new IllegalArgumentException( "Missing column mapping for nested property column " + columnMetadata.getName()); } final var columnMapping = columnMappingsIterator.next(); encodedColumn = encodeStructPropertyColumn( features, useFSST, columnMetadata, columnMapping, physicalLevelTechnique); } else { throw new IllegalArgumentException( "The specified data type for the field is currently not supported: " + columnMetadata); } featureScopedPropertyColumns.addAll(encodedColumn); } return featureScopedPropertyColumns; } private static ArrayList encodeStructPropertyColumn( SequencedCollection features, boolean useFSST, MltMetadata.Column columnMetadata, ColumnMapping columnMapping, PhysicalLevelTechnique physicalLevelTechnique) throws IOException { // TODO: add present stream for struct column /* We limit the nesting level to one in this implementation */ final var rootName = columnMetadata.getName(); if (!columnMapping.getUseSharedDictionaryEncoding()) { throw new IllegalArgumentException( "Only shared dictionary encoding is currently supported for nested property columns"); } /* Plan -> when there is a struct field and the useSharedDictionaryFlag is enabled * share the dictionary for all string columns which are located one after * the other in the sequence */ final var complexType = columnMetadata.field().type().complexType(); final var sharedDictionary = new ArrayList>(features.size() * complexType.children().size()); for (var nestedFieldMetadata : complexType.children()) { if (nestedFieldMetadata.type().scalarType() == null) { throw new IllegalArgumentException( "Nested field '" + nestedFieldMetadata.name() + "' has null scalarType"); } final var scalarType = nestedFieldMetadata.type().scalarType().physicalType(); if (scalarType != MltMetadata.ScalarType.STRING) { throw new IllegalArgumentException( "Only fields of type String are currently supported as nested property columns"); } final var propertyName = rootName + nestedFieldMetadata.name(); sharedDictionary.add( features.stream() .map( mvtFeature -> mvtFeature .findProperty(propertyName) .filter(p -> p.getType().is(MltMetadata.ScalarType.STRING)) .map(p -> p.getValue(mvtFeature.getIndex())) .flatMap(StreamUtil.optionalOfType(String.class)) .orElse(null)) .collect(Collectors.toList())); } if (sharedDictionary.stream().allMatch(List::isEmpty)) { // Set number of streams to zero if no property values are present in this tile // TODO: Can we skip the column entirely in this case? return new ArrayList<>(Arrays.asList(new byte[] {0})); } final var nestedColumns = StringEncoder.encodeSharedDictionary(sharedDictionary, physicalLevelTechnique, useFSST); final var numStreams = nestedColumns.getLeft(); final var encodedColumns = nestedColumns.getRight(); assert (numStreams > 0); // encodeSharedDictionary cannot return zero streams final var result = new ArrayList(encodedColumns.size() + 1); result.add(EncodingUtils.encodeVarint(numStreams, false)); result.addAll(encodedColumns); return result; } private static ArrayList encodeScalarPropertyColumn( SequencedCollection features, boolean useFSST, boolean coercePropertyValues, MltMetadata.Column columnMetadata, PhysicalLevelTechnique physicalLevelTechnique, @NotNull ConversionConfig.IntegerEncodingOption integerEncodingOption) throws IOException { if (MltTypeMap.Tag0x01.hasStreamCount(columnMetadata) && features.stream().noneMatch(f -> f.findProperty(columnMetadata.getName()).isPresent())) { // Indicate a missing property column in the tile with a zero for the number of streams // TODO: Can we skip the column entirely in this case? return new ArrayList<>(Arrays.asList(new byte[] {0})); } return encodeScalarPropertyColumn( columnMetadata, false, features, physicalLevelTechnique, useFSST, coercePropertyValues, integerEncodingOption); } private static Boolean getBooleanPropertyValue(@NotNull Feature feature, @NotNull String name) { return feature .findProperty(name) .filter(p -> p.getType().is(MltMetadata.ScalarType.BOOLEAN)) .map(p -> (Boolean) p.getValue(feature.getIndex())) .orElse(null); } private static Integer strictIntOrNull(@Nullable Long value) { return (value != null && value.intValue() == value.longValue()) ? value.intValue() : null; } private static Integer strictIntOrNull(@Nullable U64 value) { return (value != null && value.intValue().longValue() == value.longValue()) ? value.intValue() : null; } private static Integer strictIntOrNull(@Nullable Float value) { return (value != null && value.intValue() == value) ? value.intValue() : null; } private static Integer strictIntOrNull(@Nullable Double value) { return (value != null && value.intValue() == value) ? value.intValue() : null; } private static Long strictLongOrNull(@Nullable Float value) { return (value != null && value.longValue() == value) ? value.longValue() : null; } private static Long strictLongOrNull(@Nullable Double value) { return (value != null && value.longValue() == value) ? value.longValue() : null; } private static Float strictFloatOrNull(@Nullable Double value) { return (value != null && value.floatValue() == value) ? value.floatValue() : null; } private static Integer getIntPropertyValue(@NotNull Feature feature, @NotNull String name) { final var index = feature.getIndex(); return feature .findProperty(name) .map( p -> switch (p.getType().getScalarType().orElse(null)) { case BOOLEAN -> ((Boolean) p.getValue(index)) ? 1 : 0; case UINT_8 -> ((U8) p.getValue(index)).intValue(); case INT_8, INT_32 -> ((Number) p.getValue(index)).intValue(); case UINT_32 -> ((U32) p.getValue(index)).intValue(); case INT_64 -> strictIntOrNull((Long) p.getValue(index)); case UINT_64 -> strictIntOrNull((U64) p.getValue(index)); case FLOAT -> strictIntOrNull((Float) p.getValue(index)); case DOUBLE -> strictIntOrNull((Double) p.getValue(index)); default -> null; }) .orElse(null); } private static Long getLongPropertyValue(@NotNull Feature feature, @NotNull String name) { final var index = feature.getIndex(); return feature .findProperty(name) .map( p -> switch (p.getType().getScalarType().orElse(null)) { case BOOLEAN -> ((Boolean) p.getValue(index)) ? 1L : 0L; case UINT_8 -> ((U8) p.getValue(index)).longValue(); case UINT_32 -> ((U32) p.getValue(index)).longValue(); case INT_8, INT_32, INT_64 -> ((Number) p.getValue(index)).longValue(); case UINT_64 -> ((U64) p.getValue(index)).longValue(); case FLOAT -> strictLongOrNull((Float) p.getValue(index)); case DOUBLE -> strictLongOrNull((Double) p.getValue(index)); default -> null; }) .orElse(null); } private static Float getFloatPropertyValue(@NotNull Feature feature, @NotNull String name) { final var index = feature.getIndex(); return feature .findProperty(name) .map( p -> switch (p.getType().getScalarType().orElse(null)) { case BOOLEAN -> ((Boolean) p.getValue(index)) ? 1.0f : 0.0f; case UINT_8 -> ((U8) p.getValue(index)).intValue().floatValue(); case UINT_32 -> ((U32) p.getValue(index)).longValue().floatValue(); case UINT_64 -> ((U64) p.getValue(index)).longValue().floatValue(); case INT_8, INT_32, INT_64, FLOAT -> ((Number) p.getValue(index)).floatValue(); case DOUBLE -> strictFloatOrNull((Double) p.getValue(index)); default -> null; }) .orElse(null); } private static Double getDoublePropertyValue(@NotNull Feature feature, @NotNull String name) { final var index = feature.getIndex(); return feature .findProperty(name) .map( p -> switch (p.getType().getScalarType().orElse(null)) { case BOOLEAN -> ((Boolean) p.getValue(index)) ? 1.0 : 0.0; case UINT_8 -> ((U8) p.getValue(index)).intValue().doubleValue(); case UINT_32 -> ((U32) p.getValue(index)).longValue().doubleValue(); case UINT_64 -> ((U64) p.getValue(index)).longValue().doubleValue(); case INT_8, INT_32, INT_64, FLOAT, DOUBLE -> ((Number) p.getValue(index)).doubleValue(); default -> null; }) .orElse(null); } private static String getStringPropertyValue( @NotNull Feature feature, @NotNull String name, boolean coercePropertyValues) { final var index = feature.getIndex(); return feature .findProperty(name) .map( p -> switch (p.getType().getScalarType().orElse(null)) { case STRING -> (String) p.getValue(index); default -> coercePropertyValues ? p.getValue(index).toString() : null; }) .orElse(null); } public static ArrayList encodeScalarPropertyColumn( MltMetadata.Column columnMetadata, boolean isID, SequencedCollection features, PhysicalLevelTechnique physicalLevelTechnique, boolean useFSST, boolean coercePropertyValues, @NotNull ConversionConfig.IntegerEncodingOption integerEncodingOption) throws IOException { final var scalarType = columnMetadata .getScalarType() .orElseThrow(() -> new IllegalArgumentException("scalarType must not be null")); return switch (scalarType) { case BOOLEAN -> // no stream count encodeBooleanColumn(features, columnMetadata); case INT_32, UINT_32 -> { final var signed = (scalarType == MltMetadata.ScalarType.INT_32); // no stream count yield encodeInt32Column( features, columnMetadata, isID, physicalLevelTechnique, signed, integerEncodingOption); } case INT_64, UINT_64 -> { final var signed = (scalarType == MltMetadata.ScalarType.INT_64); // no stream count yield encodeInt64Column(features, columnMetadata, isID, signed, integerEncodingOption); } case FLOAT -> { // no stream count yield encodeFloatColumn(features, columnMetadata); } case DOUBLE -> { // no stream count yield encodeDoubleColumn(features, columnMetadata); } case STRING -> encodeStringColumn( columnMetadata, features, physicalLevelTechnique, useFSST, coercePropertyValues); default -> throw new IllegalArgumentException( "The specified scalar data type is currently not supported: " + scalarType); }; } private static ArrayList encodeStringColumn( MltMetadata.Column columnMetadata, SequencedCollection features, PhysicalLevelTechnique physicalLevelTechnique, boolean useFSST, boolean coercePropertyValues) throws IOException { /* * -> Single Column * -> Plain Encoding Stream -> present, length, data * -> Dictionary Encoding Streams -> present, length, data, dictionary * -> N Columns Dictionary * -> SharedDictionaryLength, SharedDictionary, present1, data1, present2, data2 * -> N Columns FsstDictionary * */ final var rawStringValues = features.stream() .map(f -> getStringPropertyValue(f, columnMetadata.getName(), coercePropertyValues)) .toArray(String[]::new); final var stringValues = Arrays.stream(rawStringValues).filter(Objects::nonNull).toList(); final ArrayList presentStream; if (columnMetadata.isNullable()) { final var presentValues = Arrays.stream(rawStringValues).map(Objects::nonNull).toArray(Boolean[]::new); presentStream = BooleanEncoder.encodeBooleanStream(presentValues, PhysicalStreamType.PRESENT); } else { presentStream = new ArrayList<>(); } final var stringColumn = StringEncoder.encode(stringValues, physicalLevelTechnique, useFSST); /* Plus 1 for present stream */ final var hasPresentStream = ByteArrayUtil.totalLength(presentStream) > 0; final var streamCount = stringColumn.getLeft() + (hasPresentStream ? 1 : 0); final var encodedFieldMetadata = EncodingUtils.encodeVarint(streamCount, false); final var result = new ArrayList(presentStream.size() + stringColumn.getRight().size() + 1); result.add(encodedFieldMetadata); result.addAll(presentStream); result.addAll(stringColumn.getRight()); return result; } private static ArrayList encodeBooleanColumn( SequencedCollection features, MltMetadata.Column metadata) throws IOException { final var presentStream = metadata.isNullable() ? new BitSet(features.size()) : null; final var dataStream = new BitSet(); var dataStreamIndex = 0; var presentStreamIndex = 0; for (var feature : features) { final var propertyValue = getBooleanPropertyValue(feature, metadata.getName()); final var present = (propertyValue != null); if (present) { dataStream.set(dataStreamIndex++, (boolean) propertyValue); } if (presentStream != null) { presentStream.set(presentStreamIndex++, present); } } final var encodedPresentStream = (presentStream != null) ? EncodingUtils.encodeBooleanRle(presentStream, presentStreamIndex) : new byte[0]; final var encodedDataStream = EncodingUtils.encodeBooleanRle(dataStream, dataStreamIndex); final var result = (presentStream != null) ? new StreamMetadata( PhysicalStreamType.PRESENT, null, LogicalLevelTechnique.RLE, LogicalLevelTechnique.NONE, PhysicalLevelTechnique.NONE, presentStreamIndex, encodedPresentStream.length) .encode() : new ArrayList(); final var encodedDataStreamMetadata = new StreamMetadata( PhysicalStreamType.DATA, null, LogicalLevelTechnique.RLE, LogicalLevelTechnique.NONE, PhysicalLevelTechnique.NONE, dataStreamIndex, encodedDataStream.length) .encode(); result.add(encodedPresentStream); result.addAll(encodedDataStreamMetadata); result.add(encodedDataStream); return result; } private static ArrayList encodeFloatColumn( SequencedCollection features, MltMetadata.Column metadata) throws IOException { final var values = new ArrayList(features.size()); final var presentValues = metadata.isNullable() ? new Boolean[features.size()] : null; var presentIndex = 0; for (var feature : features) { final var propertyValue = getFloatPropertyValue(feature, metadata.getName()); final var present = (propertyValue != null); if (present) { values.add(propertyValue); } if (presentValues != null) { presentValues[presentIndex++] = present; } } final var encodedPresentStream = (presentValues != null) ? BooleanEncoder.encodeBooleanStream(presentValues, PhysicalStreamType.PRESENT) : null; final var result = FloatEncoder.encodeFloatStream(values); if (encodedPresentStream != null) { result.addAll(0, encodedPresentStream); } return result; } private static ArrayList encodeDoubleColumn( SequencedCollection features, MltMetadata.Column metadata) throws IOException { final var values = new ArrayList(features.size()); final var presentValues = metadata.isNullable() ? new Boolean[features.size()] : null; var presentIndex = 0; for (var feature : features) { final var propertyValue = getDoublePropertyValue(feature, metadata.getName()); final var present = (propertyValue != null); if (present) { values.add(propertyValue); } if (presentValues != null) { presentValues[presentIndex++] = present; } } final var encodedPresentStream = (presentValues != null) ? BooleanEncoder.encodeBooleanStream(presentValues, PhysicalStreamType.PRESENT) : null; final var result = DoubleEncoder.encodeDoubleStream(values); if (encodedPresentStream != null) { result.addAll(0, encodedPresentStream); } return result; } private static ArrayList encodeInt32Column( SequencedCollection features, MltMetadata.Column metadata, boolean isID, PhysicalLevelTechnique physicalLevelTechnique, boolean isSigned, @NotNull ConversionConfig.IntegerEncodingOption integerEncodingOption) throws IOException { final var values = new ArrayList(features.size()); final var presentValues = metadata.isNullable() ? new Boolean[features.size()] : null; var presentIndex = 0; for (var feature : features) { // Force ID values to integer for this column. // If long were required, `encodeInt64Column` would have been called instead. final var propertyValue = isID ? (feature.hasId() ? Integer.valueOf(Math.toIntExact(feature.getId())) : null) : getIntPropertyValue(feature, metadata.getName()); final var present = (propertyValue != null); if (present) { values.add(propertyValue); } if (presentValues != null) { presentValues[presentIndex++] = present; } // If the column is not nullable, all values must be present. // Failure of this assertion indicates a problem with metadata creation, // or use of the metadata to encode data other than what it describes. assert (present || metadata.isNullable()); } final var encodedPresentStream = (presentValues != null) ? BooleanEncoder.encodeBooleanStream(presentValues, PhysicalStreamType.PRESENT) : null; final var result = IntegerEncoder.encodeIntStream( CollectionUtils.unboxInts(values), physicalLevelTechnique, isSigned, PhysicalStreamType.DATA, null, integerEncodingOption); if (encodedPresentStream != null) { result.addAll(0, encodedPresentStream); } return result; } private static ArrayList encodeInt64Column( SequencedCollection features, MltMetadata.Column metadata, boolean isID, boolean isSigned, @NotNull ConversionConfig.IntegerEncodingOption integerEncodingOption) throws IOException { final var values = new ArrayList(features.size()); final var presentValues = metadata.isNullable() ? new Boolean[features.size()] : null; var presentIndex = 0; for (var feature : features) { final var propertyValue = isID ? feature.idOrNull() : getLongPropertyValue(feature, metadata.getName()); final var present = (propertyValue != null); if (present) { values.add(propertyValue); } if (presentValues != null) { presentValues[presentIndex++] = present; } } final var encodedPresentStream = (presentValues != null) ? BooleanEncoder.encodeBooleanStream(presentValues, PhysicalStreamType.PRESENT) : null; final var result = IntegerEncoder.encodeLongStream( CollectionUtils.unboxLongs(values), isSigned, PhysicalStreamType.DATA, null, integerEncodingOption); if (encodedPresentStream != null) { result.addAll(0, encodedPresentStream); } return result; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/StringEncoder.java ================================================ package org.maplibre.mlt.converter.encodings; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.lang3.mutable.MutableInt; import org.apache.commons.lang3.tuple.Pair; import org.maplibre.mlt.converter.encodings.fsst.FsstEncoder; import org.maplibre.mlt.metadata.stream.DictionaryType; import org.maplibre.mlt.metadata.stream.LengthType; import org.maplibre.mlt.metadata.stream.LogicalLevelTechnique; import org.maplibre.mlt.metadata.stream.LogicalStreamType; import org.maplibre.mlt.metadata.stream.OffsetType; import org.maplibre.mlt.metadata.stream.PhysicalLevelTechnique; import org.maplibre.mlt.metadata.stream.PhysicalStreamType; import org.maplibre.mlt.metadata.stream.StreamMetadata; import org.maplibre.mlt.util.ByteArrayUtil; public class StringEncoder { private StringEncoder() {} /// Convert a collection of string columns into a shared dictionary encoded byte array /// @return Pair of (number of streams, dictionary encoded to byte array) public static Pair> encodeSharedDictionary( List> values, PhysicalLevelTechnique physicalLevelTechnique, boolean useFsstEncoding) throws IOException { /* * compare single column encoding with shared dictionary encoding * Shared dictionary layout -> length, dictionary, present1, data1, present2, data2 * Shared Fsst dictionary layout -> symbol table, symbol length, dictionary/compressed corpus, length, present1, data1, present2, data2 * */ // TODO: also compare size with plain and single encoded columns // TODO: also sort dictionary for the usage in combination with Gzip? final var dictionary = new ArrayList(values.size()); final var dictionarySize = new MutableInt(0); final var dictMap = new HashMap(values.size()); final var dataStreams = new ArrayList>(values.size()); final var presentStreams = new ArrayList(values.size()); for (var column : values) { final var presentStream = new Boolean[column.size()]; final var dataStream = new ArrayList(column.size()); presentStreams.add(presentStream); dataStreams.add(dataStream); var presentIndex = 0; for (var value : column) { presentStream[presentIndex++] = (value != null); if (value != null) { dataStream.add( dictMap.computeIfAbsent( value, v -> { final var index = dictionarySize.getAndIncrement(); dictionary.add(v); return index; })); } } } if (dictionarySize.intValue() == 0) { /* Set number of streams to zero if no columns are present in this tile */ return Pair.of(0, new ArrayList<>()); } final var encodedSharedDictionary = encodeDictionary(dictionary, physicalLevelTechnique, false, true); final ArrayList encodedSharedFsstDictionary; if (useFsstEncoding) { encodedSharedFsstDictionary = encodeFsstDictionary( dictionary, dictionarySize.intValue(), physicalLevelTechnique, false); } else { encodedSharedFsstDictionary = null; } final var usingFSST = useFsstEncoding && ByteArrayUtil.totalLength(encodedSharedFsstDictionary) < ByteArrayUtil.totalLength(encodedSharedDictionary); final var result = usingFSST ? encodedSharedFsstDictionary : encodedSharedDictionary; // stream_count = dict streams + actual streams written across all child columns. // Plain has 2 dict streams (length + data). // FSST has 4 dict streams (symbol_lengths, symbol_table, value_lengths, compressed_corpus). // There used to be a bug here where one extra column was counted var numStreams = usingFSST ? 4 : 2; for (var i = 0; i < dataStreams.size(); i++) { var presentStream = presentStreams.get(i); var dataStream = dataStreams.get(i); if (dataStream.isEmpty()) { /* If no values are present in this column add zero for number of streams */ final var encodedFieldMetadata = EncodingUtils.encodeVarint(0, false); result.add(encodedFieldMetadata); continue; } final var encodedFieldMetadata = EncodingUtils.encodeVarints(new int[] {2}, false, false); // TODO: make present stream optional final var encodedPresentStream = BooleanEncoder.encodeBooleanStream(presentStream, PhysicalStreamType.PRESENT); numStreams += 2; final var encodedDataStream = IntegerEncoder.encodeIntStream( dataStream, physicalLevelTechnique, false, PhysicalStreamType.OFFSET, new LogicalStreamType(OffsetType.STRING)); result.add(encodedFieldMetadata); result.addAll(encodedPresentStream); result.addAll(encodedDataStream); } return Pair.of(numStreams, result); } public static Pair> encode( Collection values, PhysicalLevelTechnique physicalLevelTechnique, boolean useFsstEncoding) throws IOException { /* * convert a single string column -> check if plain, dictionary, fsst or fsstDictionary * -> plain -> length, data * -> dictionary -> length, dictionary, data * -> fsst -> symbol length, symbol table, length, compressed corpus * -> fsst dictionary -> symbol length, symbol table, length, dictionary/compressed corpus, offset * Schema selection * -> based on statistics if dictionary encoding is used * -> compare four possible encodings in size based on samples * */ final var plainEncodedColumn = encodePlain(values, physicalLevelTechnique); final var dictionaryEncodedColumn = encodeDictionary(values, physicalLevelTechnique, true, false); final ArrayList fsstEncodedDictionary; if (useFsstEncoding) { fsstEncodedDictionary = encodeFsstDictionary(values, values.size(), physicalLevelTechnique, true); } else { fsstEncodedDictionary = null; } return Stream.of( Pair.of(2, plainEncodedColumn), Pair.of(3, dictionaryEncodedColumn), Pair.of(5, fsstEncodedDictionary)) .filter(p -> p.getRight() != null) .min(Comparator.comparingInt(a -> ByteArrayUtil.totalLength(a.getRight()))) .orElseThrow(); } private static ArrayList encodeFsstDictionary( Collection values, int valueCount, PhysicalLevelTechnique physicalLevelTechnique, boolean encodeDataStream) throws IOException { final var dataStream = new ArrayList(valueCount); final var dictionary = new ArrayList(valueCount); final var dictMap = new HashMap(valueCount); for (var value : values) { dataStream.add( dictMap.computeIfAbsent( value, v -> { dictionary.add(v); return dictionary.size() - 1; })); } final var symbolTable = encodeFsst(dictionary, physicalLevelTechnique, false); if (!encodeDataStream) { return symbolTable; } final var encodedDataStream = IntegerEncoder.encodeIntStream( dataStream, physicalLevelTechnique, false, PhysicalStreamType.OFFSET, new LogicalStreamType(OffsetType.STRING)); symbolTable.addAll(encodedDataStream); return symbolTable; } private static ArrayList encodeFsst( Collection values, PhysicalLevelTechnique physicalLevelTechnique, @SuppressWarnings("SameParameterValue") boolean isSharedDictionary) throws IOException { final var joinedValues = String.join("", values).getBytes(StandardCharsets.UTF_8); final var symbolTable = FsstEncoder.encode(joinedValues); final var encodedSymbols = symbolTable.symbols(); final var compressedCorpus = symbolTable.compressedData(); final var symbolLengths = Arrays.stream(symbolTable.symbolLengths()).boxed().collect(Collectors.toList()); final var encodedSymbolLengths = IntegerEncoder.encodeIntStream( symbolLengths, physicalLevelTechnique, false, PhysicalStreamType.LENGTH, new LogicalStreamType(LengthType.SYMBOL)); final var symbolTableMetadata = new StreamMetadata( PhysicalStreamType.DATA, new LogicalStreamType(DictionaryType.FSST), LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, PhysicalLevelTechnique.NONE, // TODO: numValues in this context not needed -> set 0 to save space -> only 1 byte // with varint? symbolLengths.size(), encodedSymbols.length) .encode(); final var lengthStream = values.stream().mapToInt(v -> v.getBytes(StandardCharsets.UTF_8).length).toArray(); final var encodedLengthStream = IntegerEncoder.encodeIntStream( lengthStream, physicalLevelTechnique, false, PhysicalStreamType.LENGTH, new LogicalStreamType(LengthType.DICTIONARY)); final var compressedCorpusStreamMetadata = new StreamMetadata( PhysicalStreamType.DATA, new LogicalStreamType( isSharedDictionary ? DictionaryType.SHARED : DictionaryType.SINGLE), LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, PhysicalLevelTechnique.NONE, values.size(), compressedCorpus.length) .encode(); /* SymbolLength, SymbolTable, Value Length, Compressed Corpus */ final var result = encodedSymbolLengths; result.addAll(symbolTableMetadata); result.add(symbolTable.symbols()); result.addAll(encodedLengthStream); result.addAll(compressedCorpusStreamMetadata); result.add(compressedCorpus); return result; } private static int totalLengthOf(Collection values) { return values.stream().mapToInt(String::length).sum(); } private static ArrayList encodeDictionary( Collection values, PhysicalLevelTechnique physicalLevelTechnique, boolean encodeOffsetStream, boolean isSharedDictionary) throws IOException { final var offsetStream = new ArrayList(values.size()); final var lengthStream = new ArrayList(values.size()); final var dictionary = new ArrayList(values.size()); final var dictMap = new HashMap(values.size()); final var dictionaryStream = new ByteArrayOutputStream(totalLengthOf(values)); for (var value : values) { offsetStream.add( dictMap.computeIfAbsent( value, v -> { dictionary.add(v); final var utf8EncodedData = value.getBytes(StandardCharsets.UTF_8); lengthStream.add(utf8EncodedData.length); try { dictionaryStream.write(utf8EncodedData); } catch (IOException ex) { throw new UncheckedIOException(ex); } return dictionary.size() - 1; })); } final var dictionaryData = dictionaryStream.toByteArray(); final var encodedLengthStream = IntegerEncoder.encodeIntStream( lengthStream, physicalLevelTechnique, false, PhysicalStreamType.LENGTH, new LogicalStreamType(LengthType.DICTIONARY)); final var encodedDictionaryStreamMetadata = new StreamMetadata( PhysicalStreamType.DATA, new LogicalStreamType( isSharedDictionary ? DictionaryType.SHARED : DictionaryType.SINGLE), LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, PhysicalLevelTechnique.NONE, dictionary.size(), dictionaryData.length) .encode(); if (!encodeOffsetStream) { final var result = encodedLengthStream; result.addAll(encodedDictionaryStreamMetadata); result.add(dictionaryData); return result; } final var encodedOffsetStream = IntegerEncoder.encodeIntStream( offsetStream, physicalLevelTechnique, false, PhysicalStreamType.OFFSET, new LogicalStreamType(OffsetType.STRING)); /* Length, Offset (String), Data (Dictionary -> Single) */ final var result = encodedLengthStream; result.addAll(encodedOffsetStream); result.addAll(encodedDictionaryStreamMetadata); result.add(dictionaryData); return result; } public static ArrayList encodePlain( Collection values, PhysicalLevelTechnique physicalLevelTechnique) throws IOException { final var lengthStream = new ArrayList(values.size()); final var dataStream = new ByteArrayOutputStream(totalLengthOf(values)); for (var value : values) { final var utf8EncodedValue = value.getBytes(StandardCharsets.UTF_8); dataStream.write(utf8EncodedValue); lengthStream.add(utf8EncodedValue.length); } final var stringData = dataStream.toByteArray(); final var encodedLengthStream = IntegerEncoder.encodeIntStream( lengthStream, physicalLevelTechnique, false, PhysicalStreamType.LENGTH, new LogicalStreamType(LengthType.VAR_BINARY)); final var dataStreamMetadata = new StreamMetadata( PhysicalStreamType.DATA, new LogicalStreamType(DictionaryType.NONE), LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, PhysicalLevelTechnique.NONE, values.size(), stringData.length) .encode(); final var result = encodedLengthStream; result.addAll(dataStreamMetadata); result.add(stringData); return result; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/fsst/Fsst.java ================================================ package org.maplibre.mlt.converter.encodings.fsst; import java.io.ByteArrayOutputStream; public interface Fsst { SymbolTable encode(byte[] data); default byte[] decode(SymbolTable encoded) { return decode( encoded.symbols(), encoded.symbolLengths(), encoded.compressedData(), encoded.decompressedLength()); } default byte[] decode( byte[] symbols, int[] symbolLengths, byte[] compressedData, int decompressedLength) { // optimized decoder that knows the output size so pre-allocates the array to avoid dynamically // allocating a ByteArrayOutputStream int idx = 0; byte[] output = new byte[decompressedLength]; int[] symbolOffsets = new int[symbolLengths.length]; for (int i = 1; i < symbolLengths.length; i++) { symbolOffsets[i] = symbolOffsets[i - 1] + symbolLengths[i - 1]; } for (int i = 0; i < compressedData.length; i++) { // In Java a byte[] is signed [-128 to 127], whereas in C++ it is unsigned [0 to 255] // So we do a bit shifting operation to convert the values into unsigned values for easier // handling int symbolIndex = compressedData[i] & 0xFF; // 255 is our escape byte -> take the next symbol as it is if (symbolIndex == 255) { output[idx++] = compressedData[++i]; } else if (symbolIndex < symbolLengths.length) { int len = symbolLengths[symbolIndex]; System.arraycopy(symbols, symbolOffsets[symbolIndex], output, idx, len); idx += len; } } return output; } /** * @deprecated use {@link #decode(byte[], int[], byte[], int)} instead with an explicit length */ @Deprecated default byte[] decode(byte[] symbols, int[] symbolLengths, byte[] compressedData) { ByteArrayOutputStream decodedData = new ByteArrayOutputStream(); int[] symbolOffsets = new int[symbolLengths.length]; for (int i = 1; i < symbolLengths.length; i++) { symbolOffsets[i] = symbolOffsets[i - 1] + symbolLengths[i - 1]; } for (int i = 0; i < compressedData.length; i++) { // In Java a byte[] is signed [-128 to 127], whereas in C++ it is unsigned [0 to 255] // So we do a bit shifting operation to convert the values into unsigned values for easier // handling int symbolIndex = compressedData[i] & 0xFF; // 255 is our escape byte -> take the next symbol as it is if (symbolIndex == 255) { decodedData.write(compressedData[++i] & 0xFF); } else if (symbolIndex < symbolLengths.length) { decodedData.write(symbols, symbolOffsets[symbolIndex], symbolLengths[symbolIndex]); } } return decodedData.toByteArray(); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/fsst/FsstDebug.java ================================================ package org.maplibre.mlt.converter.encodings.fsst; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; class FsstDebug implements Fsst { private final Fsst java = new FsstJava(); private final Fsst jni = new FsstJni(); private static final AtomicLong jniTime = new AtomicLong(); private static final AtomicLong javaTime = new AtomicLong(); private static final AtomicLong jniSize = new AtomicLong(); private static final AtomicLong javaSize = new AtomicLong(); private static final AtomicInteger javaSmaller = new AtomicInteger(); private static final AtomicInteger jniSmaller = new AtomicInteger(); private static final AtomicBoolean printed = new AtomicBoolean(false); static { Runtime.getRuntime() .addShutdownHook( new Thread( () -> { System.err.print(FsstDebug.printStatsOnce()); })); } @Override public SymbolTable encode(byte[] data) { final long a = System.currentTimeMillis(); final var fromJni = jni.encode(data); final long b = System.currentTimeMillis(); final var fromJava = java.encode(data); final long c = System.currentTimeMillis(); jniTime.addAndGet(b - a); javaTime.addAndGet(c - b); jniSize.addAndGet(fromJni.weight()); javaSize.addAndGet(fromJava.weight()); (fromJava.weight() <= fromJni.weight() ? javaSmaller : jniSmaller).incrementAndGet(); return fromJava; } public static String printStats() { return new StringBuilder() .append("java/jni:") .append(javaTime) .append("ms/") .append(jniTime) .append("ms ") .append(javaSize) .append("/") .append(jniSize) .append(" ") .append(javaSize.get() * 1d / jniSize.get()) .append(" jni smaller ") .append(jniSmaller) .append("/") .append(javaSmaller.get() + jniSmaller.get()) .append("\n") .toString(); } public static String printStatsOnce() { return printed.getAndSet(true) ? "" : printStats(); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/fsst/FsstEncoder.java ================================================ package org.maplibre.mlt.converter.encodings.fsst; public class FsstEncoder { public static Fsst INSTANCE; public static boolean useNative(boolean value) { if (value) { if (!FsstJni.isLoaded()) { INSTANCE = new FsstJni(); return true; } else { return false; } } else { INSTANCE = new FsstJava(); return true; } } private static Fsst getInstance() { if (INSTANCE == null) { useNative(false); } return INSTANCE; } private FsstEncoder() {} public static SymbolTable encode(byte[] data) { return getInstance().encode(data); } public static byte[] decode( byte[] symbols, int[] symbolLengths, byte[] compressedData, int decompressedLength) { return getInstance().decode(symbols, symbolLengths, compressedData, decompressedLength); } /** * @deprecated use {@link #decode(byte[], int[], byte[], int)} instead with an explicit length */ @Deprecated public static byte[] decode(byte[] symbols, int[] symbolLengths, byte[] compressedData) { return getInstance().decode(symbols, symbolLengths, compressedData); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/fsst/FsstJava.java ================================================ package org.maplibre.mlt.converter.encodings.fsst; class FsstJava implements Fsst { @Override public SymbolTable encode(byte[] data) { return SymbolTableBuilder.encode(data); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/fsst/FsstJni.java ================================================ package org.maplibre.mlt.converter.encodings.fsst; import java.nio.file.FileSystems; public class FsstJni implements Fsst { private static boolean isLoaded = false; private static UnsatisfiedLinkError loadError; static { String os = System.getProperty("os.name").toLowerCase(); boolean isWindows = os.contains("win"); String moduleDir = "build/FsstWrapper.so"; if (isWindows) { // TODO: figure out how to get cmake to put in common directory moduleDir = "build/Release/FsstWrapper.so"; } String modulePath = FileSystems.getDefault().getPath(moduleDir).normalize().toAbsolutePath().toString(); try { System.load(modulePath); isLoaded = true; } catch (UnsatisfiedLinkError e) { loadError = e; } } public SymbolTable encode(byte[] data) { return FsstJni.compress(data); } public static boolean isLoaded() { return isLoaded; } public static UnsatisfiedLinkError getLoadError() { return loadError; } public static native SymbolTable compress(byte[] inputBytes); } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/fsst/Symbol.java ================================================ package org.maplibre.mlt.converter.encodings.fsst; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; /** * A candidate symbol used when building the FSST table. * *

The first pass of FSST encoding builds single-byte symbols found in the data to encode, and * each subsequent pass combines those symbols into larger ones. */ class Symbol implements Comparable { private final byte[] bytes; // performance optimizations: precompute hashcode and store length to save an extra dereference private final int length; private final int hashcode; private Symbol(byte[] bytes, int hashcode) { this.bytes = bytes; this.length = bytes.length; this.hashcode = hashcode; } public static Symbol of(int b) { return new Symbol(new byte[] {(byte) b}, 31 + (byte) b); } public static Symbol concat(Symbol a, Symbol b) { int len = Math.min(SymbolTableBuilder.MAX_SYMBOL_LENGTH, a.length + b.length); byte[] result = new byte[len]; System.arraycopy(a.bytes, 0, result, 0, a.length); int todo = len - a.length; if (todo > 0) System.arraycopy(b.bytes, 0, result, a.length, todo); int hash = a.hashcode; for (byte bb : b.bytes) { hash = 31 * hash + bb; } return new Symbol(result, hash); } @Override public boolean equals(Object o) { return (this == o) || (o instanceof Symbol other && Arrays.equals(bytes, other.bytes)); } @Override public int hashCode() { return hashcode; } public int length() { return length; } public byte[] bytes() { return bytes; } @Override public int compareTo(Symbol o) { // sort symbols lexicographically except longer symbols are before symbols that are their prefix if (this == o) return 0; var a = bytes; var b = o.bytes; int i = Arrays.mismatch(a, b); if (i >= 0 && i < a.length && i < b.length) { return Byte.compareUnsigned(a[i], b[i]); } // only difference from Arrays.compareUnsigned - when one is a prefix // of the other, sort the longest one first return b.length - a.length; } public int first() { return bytes[0] & 0xFF; } /** * Returns true if this symbol appears at index {@code offset} in {@code text}, skipping the first * {@code ignore} bytes if we already know they match. */ public boolean match(ByteBuffer text, int offset, int ignore) { if (text.capacity() < offset + length) { return false; } // optimization: when symbols are indexed by first 1 or 2 bytes, we already know those bytes // match so don't need to check them for (int i = ignore; i < length; i++) { if (text.get(offset + i) != bytes[i]) { return false; } } return true; } @Override public String toString() { return "Symbol[" + new String(bytes, StandardCharsets.UTF_8) + "]"; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/fsst/SymbolTable.java ================================================ package org.maplibre.mlt.converter.encodings.fsst; import java.util.Arrays; /** * Output of FSST-encoding: a symbol table, and text encoded with that symbol table. * * @param symbols Up to 255 symbols in the table concatenated together * @param symbolLengths Lengths of each of those 255 symbols in the table * @param compressedData Encoded text where a value less than 255 refers to that symbol from the * table, and a value of 255 means the next byte should be used directly. * @param decompressedLength The length of the text after decompression */ public record SymbolTable( byte[] symbols, int[] symbolLengths, byte[] compressedData, int decompressedLength) { @Override public String toString() { return "SymbolTable{weight=" + weight() + ", symbols=byte[" + symbols.length + "], symbolLengths=int[" + symbolLengths.length + "], compressedData=byte[" + compressedData.length + "], decompressedLength=" + decompressedLength + '}'; } public int weight() { return symbols.length + symbolLengths.length + compressedData.length; } @Override public boolean equals(Object o) { return this == o || (o instanceof SymbolTable that && Arrays.equals(symbols, that.symbols) && Arrays.equals(symbolLengths, that.symbolLengths) && Arrays.equals(compressedData, that.compressedData)); } @Override public int hashCode() { int result = Arrays.hashCode(symbols); result = 31 * result + Arrays.hashCode(symbolLengths); result = 31 * result + Arrays.hashCode(compressedData); return result; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/fsst/SymbolTableBuilder.java ================================================ /* MIT License Copyright (c) 2018-2020, CWI, TU Munich, FSU Jena Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package org.maplibre.mlt.converter.encodings.fsst; import com.carrotsearch.hppc.ByteArrayList; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.PriorityQueue; import java.util.stream.IntStream; import org.jetbrains.annotations.NotNull; /** * Port of the FSST-encoding algorithm from github.com/cwida/fsst. * *

Many of the C++ specific optimizations are excluded or replaced with the simpler python logic * described in fsstcompression.pdf to * improve readability. * *

Some Java-specific optimizations are added to bring performance within 50% of the C++ version * on real-world data. */ class SymbolTableBuilder { static final int MAX_SYMBOL_LENGTH = 8; private static final int NUM_ITERS = 6; public static final int DEFAULT_SAMPLE_SIZE = 30_000; private final int sampleSize; private final Symbol[] symbols = new Symbol[512]; /** Index single-byte symbol in symbols array by their only byte. */ private final int[] sIndex = new int[256]; /** Index multi-byte symbols in symbols array by their first 2 bytes. */ private final int[] sIndexByFirst2 = new int[(1 << 16) + 1]; /** * Map from index in {@link #symbols} to a new index that sorts symbols by length ascending with * single-byte symbols last */ private int[] sIndexByLength; /** Map from index sorted by length to original index in {@link #symbols} */ private int[] sIndexByLengthReverse; private int nSymbols; private SymbolTableBuilder(int sampleSize) { for (int code = 0; code < 256; code++) { symbols[code] = Symbol.of(code); } this.sampleSize = sampleSize; } /** Builds a symbol table with up to a 30kb sample and compresses {@code data} with it. */ public static SymbolTable encode(byte[] data) { var buf = ByteBuffer.wrap(data); return buildSymbolTable(buf, DEFAULT_SAMPLE_SIZE).encode(buf); } public static SymbolTableBuilder buildSymbolTable(ByteBuffer data, int sampleSize) { // main loop: init symbol table with single-byte symbols... SymbolTableBuilder st = new SymbolTableBuilder(sampleSize); SymbolTableBuilder bestTable = st; long bestWeight = Long.MAX_VALUE; Counters counters; Counters bestCounters = null; for (int i = 1; i <= NUM_ITERS; i++) { // then gather statistics about symbol frequencies and encoded data size counters = new Counters(); long weight = st.compressCount(counters, data, i < NUM_ITERS); if (weight <= bestWeight) { bestCounters = counters; bestTable = st; bestWeight = weight; } // and iteratively combine symbols and return the best one if (i < NUM_ITERS) st = st.makeTable(counters, false, sampleSize < data.capacity()); } var result = bestTable.makeTable(bestCounters, true, sampleSize < data.capacity()); return result.sortSymbolsByLength(); } private SymbolTableBuilder sortSymbolsByLength() { // sort symbols by length ascending, with length 1 symbols last sIndexByLength = new int[nSymbols]; sIndexByLengthReverse = new int[nSymbols]; int idx = 0; // 2, 3, 4, 5, 6, 7, 8, 1 for (int b = 2; b <= MAX_SYMBOL_LENGTH + 1; b++) { int len = b > MAX_SYMBOL_LENGTH ? 1 : b; for (int i = 0; i < nSymbols; i++) { var symbol = symbols[256 + i]; if (symbol.length() == len) { int j = idx++; sIndexByLength[i] = j; sIndexByLengthReverse[j] = i; } } } return this; } private SymbolTable encode(ByteBuffer text) { var encodedSymbols = new ByteArrayList(); int[] lengths = new int[nSymbols]; for (int i = 0; i < nSymbols; i++) { var symbol = this.symbols[256 + sIndexByLengthReverse[i]]; lengths[i] = symbol.length(); encodedSymbols.add(symbol.bytes()); } byte[] encodedText = encodeText(text, lengths); return new SymbolTable(encodedSymbols.toArray(), lengths, encodedText, text.capacity()); } private byte[] encodeText(ByteBuffer text, int[] lens) { int cap = text.capacity(); var encoded = new ByteArrayList(cap); for (int i = 0; i < cap; ) { int code = findLongestSymbol(text, i); if (isEscapeCode(code)) { encoded.add((byte) 255, text.get(i++)); } else { int symbol = sIndexByLength[code - 256]; encoded.add((byte) symbol); i += lens[symbol]; } } return encoded.toArray(); } private static boolean isEscapeCode(int code) { return code < 256; } private static void addOrInc(Map cands, Symbol s, long count, int min) { if (count >= min) { long gain = count * s.length(); cands.merge(s, gain, Long::sum); } } private void add(Symbol symbol) { symbols[256 + (nSymbols++)] = symbol; } private int findLongestSymbol(ByteBuffer text, int offset) { // first look for a multi-byte symbol starting with the next 2 bytes if (text.capacity() - offset >= 2) { int a = text.getShort(offset) & 0xFFFF; int start = sIndexByFirst2[a]; if (start > 0) { int end = sIndexByFirst2[a + 1]; for (int code = start; code < end; code++) { if (symbols[code].match(text, offset, 2)) { return code; } } } } // if not found, then look for a single-byte symbol var letter = text.get(offset) & 0xFF; int code = sIndex[letter]; if (!isEscapeCode(code)) { return code; } // otherwise just return the "escape code" for this symbol since it's not in the table return letter; } record Range(int start, int end) {} private List ranges(int size) { if (size < sampleSize) { return List.of(new Range(0, size)); } else { int chunkSize = 1000; int samples = sampleSize / chunkSize; int offset = size / (samples); return IntStream.range(0, samples) .mapToObj(i -> new Range(i * offset, Math.min(size, i * offset + chunkSize))) .toList(); } } private long compressCount(Counters counters, ByteBuffer text, boolean secondPass) { if (text.capacity() == 0) return 0; long weight = 0; for (var range : ranges(text.capacity())) { int start = range.start; int end = range.end; int code2; int code1 = findLongestSymbol(text, start); Symbol symbol = symbols[code1]; int cur = start + symbol.length(); start = cur; weight += isEscapeCode(code1) ? 2 : 1; while (cur < end) { // count single symbol (i.e. an option is not extending it) counters.count1Inc(code1); // as an alternative, consider just using the next byte.. if (symbol.length() > 1) { // .. but do not count single byte symbols doubly counters.count1Inc(text.get(start) & 0xFF); } // now match a new symbol start = cur; code2 = findLongestSymbol(text, cur); Symbol symbol2 = symbols[code2]; cur += symbol2.length(); weight += isEscapeCode(code2) ? 2 : 1; if (secondPass) { // no need to count pairs in final round // consider the symbol that is the concatenation of the two last symbols counters.count2Inc(code1, code2); // as an alternative, consider just extending with the next byte.. if (symbol2.length() > 1) { // ..but do not count single byte extensions doubly counters.count2Inc(code1, text.get(start) & 0xFF); } } code1 = code2; symbol = symbols[code1]; } } // account for the encoded symbol table size for (int i = 0; i < nSymbols; i++) { weight += symbols[256 + i].length() + 1; } return weight; } private SymbolTableBuilder makeTable(Counters counters, boolean lastPass, boolean sampled) { int minCount = 5; // hashmap of c (needed because we can generate duplicate candidates) Map cands = new HashMap<>(); int max = 256 + nSymbols; for (int pos1 = 0; pos1 < max; pos1++) { int cnt1 = counters.count1GetNext(pos1); if (cnt1 <= 0) continue; Symbol s1 = symbols[pos1]; // note from C++ implementation: // heuristic: promoting single-byte symbols (*8) helps reduce exception rates and increases // [de]compression speed addOrInc( cands, s1, (s1.length() == 1 ? 8L : 1L) * cnt1, (lastPass && !sampled) ? 1 : minCount); // don't need pair-wise counts for last pass to just encode the data if (lastPass || s1.length() == MAX_SYMBOL_LENGTH) continue; for (int pos2 = 0; pos2 < max; pos2++) { int cnt2 = counters.count2GetNext(pos1, pos2); if (cnt2 < minCount) continue; addOrInc(cands, Symbol.concat(s1, symbols[pos2]), cnt2, minCount); } } PriorityQueue pq = new PriorityQueue<>(); for (var entry : cands.entrySet()) { pq.add(new QSymbol(entry.getValue(), entry.getKey())); } SymbolTableBuilder st = new SymbolTableBuilder(sampleSize); while (st.nSymbols < 255 && !pq.isEmpty()) { var symb = pq.remove(); if (!lastPass || sampled) { st.add(symb.symbol); } else { // adding a symbol costs length + 1, so don't add if it costs more than it saves long costs = symb.symbol.length() + 1L; long saves = symb.symbol.length() == 1 ? symb.gain / 8 : symb.gain; if (saves > costs) { st.add(symb.symbol); } } } return st.finish(); } public SymbolTableBuilder finish() { Symbol[] tmp = Arrays.copyOfRange(symbols, 256, 256 + nSymbols); Arrays.sort(tmp); // sorts prefix symbols after the longer symbols for (int i = nSymbols - 1; i >= 0; i--) { int letter = tmp[i].first(); // index multi-byte by their first 2 bytes if (tmp[i].length() >= 2) { var bytes = tmp[i].bytes(); int val = ((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF); sIndexByFirst2[val] = 256 + i; // there might be symbols with this prefix, so store end of the range by setting val+1 if (sIndexByFirst2[val + 1] == 0) { sIndexByFirst2[val + 1] = 256 + i + 1; } } else { // index single-byte symbols by their only byte sIndex[letter] = 256 + i; } symbols[256 + i] = tmp[i]; } return this; } private record QSymbol(long gain, Symbol symbol) implements Comparable { @Override public int compareTo(@NotNull SymbolTableBuilder.QSymbol o) { return Long.compare(o.gain, gain); } } static class Counters { private final int[] count1 = new int[512]; private final int[] count2 = new int[512 * 512]; public void count1Inc(int pos1) { count1[pos1]++; } public void count2Inc(int pos1, int pos2) { count2[(pos1 << 9) | pos2]++; } public int count1GetNext(int pos1) { return count1[pos1]; } public int count2GetNext(int pos1, int pos2) { return count2[(pos1 << 9) | pos2]; } } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/geometry/GeometryType.java ================================================ package org.maplibre.mlt.converter.geometry; public enum GeometryType { POINT, LINESTRING, POLYGON, MULTIPOINT, MULTILINESTRING, MULTIPOLYGON } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/geometry/GeometryUtils.java ================================================ package org.maplibre.mlt.converter.geometry; import com.google.common.collect.Lists; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.TreeMap; import java.util.stream.IntStream; import org.apache.commons.lang3.tuple.Triple; public class GeometryUtils { private GeometryUtils() {} public static void sortVertexOffsets( List numParts, int[] mortonEncodedDictionaryOffsetsIn, List featureIds) { List mortonEncodedDictionaryOffsets = IntStream.of(mortonEncodedDictionaryOffsetsIn).boxed().toList(); // TODO: use an different proper optimization approach /* * Quick and dirty approach to sort the VertexOffsets of a VertexBuffer to reduce the deltas * and therefore the overall size. * The offsets are sorted based on the morton code of the first vertex of a LineString. * The order of the offsets of a LineString has to be preserved. * */ var sortedDictionaryOffsets = new TreeMap, List, List>>(); var partOffsetCounter = 0; var idCounter = 0; for (var numPart : numParts) { var currentLineVertexOffsets = mortonEncodedDictionaryOffsets.subList(partOffsetCounter, partOffsetCounter + numPart); partOffsetCounter += numPart; var featureId = featureIds.get(idCounter++); var minVertexOffset = currentLineVertexOffsets.get(0); if (sortedDictionaryOffsets.containsKey(minVertexOffset)) { var sortedDictionaryOffset = sortedDictionaryOffsets.get(minVertexOffset); sortedDictionaryOffset.getLeft().add(featureId); sortedDictionaryOffset.getMiddle().addAll(currentLineVertexOffsets); sortedDictionaryOffset.getRight().add(numPart); } else { sortedDictionaryOffsets.put( minVertexOffset, Triple.of( Lists.newArrayList(featureId), new ArrayList<>(currentLineVertexOffsets), Lists.newArrayList(numPart))); } } var sortedOffsets = sortedDictionaryOffsets.values().stream().flatMap(e -> e.getMiddle().stream()).toList(); var updatedFeatureIds = sortedDictionaryOffsets.values().stream().flatMap(e -> e.getLeft().stream()).toList(); var updatedNumParts = sortedDictionaryOffsets.values().stream().flatMap(e -> e.getRight().stream()).toList(); int i = 0; for (var morton : sortedOffsets) { mortonEncodedDictionaryOffsetsIn[i++] = morton; } featureIds.clear(); featureIds.addAll(updatedFeatureIds); numParts.clear(); numParts.addAll(updatedNumParts); } public static void sortPoints( List points, HilbertCurve hilbertCurve, List featureIds) { var sortedPoints = new ArrayList>(); for (var i = 0; i < points.size(); i++) { var featureId = featureIds.get(i); var point = points.get(i); var hilbertId = hilbertCurve.encode(point); sortedPoints.add(Triple.of(hilbertId, featureId, point)); } sortedPoints.sort(Comparator.comparingInt(Triple::getLeft)); featureIds.clear(); featureIds.addAll(sortedPoints.stream().map(Triple::getMiddle).toList()); points.clear(); points.addAll(sortedPoints.stream().map(Triple::getRight).toList()); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/geometry/Hilbert.java ================================================ package org.maplibre.mlt.converter.geometry; /** * Fast hilbert curve calculations from https://github.com/rawrunprotected/hilbert_curves * (public domain). */ public class Hilbert { private Hilbert() { throw new IllegalStateException("Utility class"); } private static int deinterleave(int tx) { tx = tx & 0x55555555; tx = (tx | (tx >>> 1)) & 0x33333333; tx = (tx | (tx >>> 2)) & 0x0F0F0F0F; tx = (tx | (tx >>> 4)) & 0x00FF00FF; tx = (tx | (tx >>> 8)) & 0x0000FFFF; return tx; } private static int interleave(int tx) { tx = (tx | (tx << 8)) & 0x00FF00FF; tx = (tx | (tx << 4)) & 0x0F0F0F0F; tx = (tx | (tx << 2)) & 0x33333333; tx = (tx | (tx << 1)) & 0x55555555; return tx; } private static int prefixScan(int tx) { tx = (tx >>> 8) ^ tx; tx = (tx >>> 4) ^ tx; tx = (tx >>> 2) ^ tx; tx = (tx >>> 1) ^ tx; return tx; } /** * Returns the x/y coordinates from hilbert index {@code pos} at {@code level} packed into {x, y} * coordinates. */ public static int[] hilbertPositionToXY(int level, int pos) { pos = pos << (32 - 2 * level); int i0 = deinterleave(pos); int i1 = deinterleave(pos >>> 1); int t0 = (i0 | i1) ^ 0xFFFF; int t1 = i0 & i1; int prefixT0 = prefixScan(t0); int prefixT1 = prefixScan(t1); int a = (((i0 ^ 0xFFFF) & prefixT1) | (i0 & prefixT0)); int resultX = (a ^ i1) >>> (16 - level); int resultY = (a ^ i0 ^ i1) >>> (16 - level); return new int[] {resultX, resultY}; } /** Returns the hilbert index at {@code level} for an x/y coordinate. */ public static int hilbertXYToIndex(int level, int x, int y) { x = x << (16 - level); y = y << (16 - level); int hA, hB, hC, hD; int a1 = x ^ y; int b1 = 0xFFFF ^ a1; int c1 = 0xFFFF ^ (x | y); int d1 = x & (y ^ 0xFFFF); hA = a1 | (b1 >>> 1); hB = (a1 >>> 1) ^ a1; hC = ((c1 >>> 1) ^ (b1 & (d1 >>> 1))) ^ c1; hD = ((a1 & (c1 >>> 1)) ^ (d1 >>> 1)) ^ d1; int a2 = hA; int b2 = hB; int c2 = hC; int d2 = hD; hA = ((a2 & (a2 >>> 2)) ^ (b2 & (b2 >>> 2))); hB = ((a2 & (b2 >>> 2)) ^ (b2 & ((a2 ^ b2) >>> 2))); hC ^= ((a2 & (c2 >>> 2)) ^ (b2 & (d2 >>> 2))); hD ^= ((b2 & (c2 >>> 2)) ^ ((a2 ^ b2) & (d2 >>> 2))); int a3 = hA; int b3 = hB; int c3 = hC; int d3 = hD; hA = ((a3 & (a3 >>> 4)) ^ (b3 & (b3 >>> 4))); hB = ((a3 & (b3 >>> 4)) ^ (b3 & ((a3 ^ b3) >>> 4))); hC ^= ((a3 & (c3 >>> 4)) ^ (b3 & (d3 >>> 4))); hD ^= ((b3 & (c3 >>> 4)) ^ ((a3 ^ b3) & (d3 >>> 4))); int a4 = hA; int b4 = hB; int c4 = hC; int d4 = hD; hC ^= ((a4 & (c4 >>> 8)) ^ (b4 & (d4 >>> 8))); hD ^= ((b4 & (c4 >>> 8)) ^ ((a4 ^ b4) & (d4 >>> 8))); int a = hC ^ (hC >>> 1); int b = hD ^ (hD >>> 1); int i0 = x ^ y; int i1 = b | (0xFFFF ^ (i0 | a)); return ((interleave(i1) << 1) | interleave(i0)) >>> (32 - 2 * level); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/geometry/HilbertCurve.java ================================================ package org.maplibre.mlt.converter.geometry; public class HilbertCurve extends SpaceFillingCurve { public HilbertCurve(int minVertexValue, int maxVertexValue) { super(minVertexValue, maxVertexValue); } public int encode(Vertex vertex) { var shiftedX = vertex.x() + coordinateShift; var shiftedY = vertex.y() + coordinateShift; return Hilbert.hilbertXYToIndex(numBits, shiftedX, shiftedY); } public int[] decode(int hilbertIndex) { return Hilbert.hilbertPositionToXY(numBits, hilbertIndex); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/geometry/SpaceFillingCurve.java ================================================ package org.maplibre.mlt.converter.geometry; public abstract class SpaceFillingCurve { protected int tileExtent; protected int numBits; protected int coordinateShift; private final int minBound; private final int maxBound; public static final int MAX_SUPPORTED_BITS = 16; public static final int MAX_SUPPORTED_RANGE = (1 << MAX_SUPPORTED_BITS) - 1; public static boolean isRangeSupported(int minVertexValue, int maxVertexValue) { return (maxVertexValue + getCoordinateShift(minVertexValue)) <= MAX_SUPPORTED_RANGE; } public SpaceFillingCurve(int minVertexValue, int maxVertexValue) { // TODO: fix tile buffer problem /* Each tile can have a buffer around, which means the coordinate values are extended beyond the specified tileExtent. * Currently we are extending size tile size be half of to the size into each direction, which works well for the test tilesets. * But this leads to problems if the tile coordinates are not within this boundaries. * */ this.coordinateShift = getCoordinateShift(minVertexValue); this.tileExtent = maxVertexValue + coordinateShift; this.numBits = (int) Math.ceil((Math.log(this.tileExtent + 1) / Math.log(2))); this.minBound = minVertexValue; this.maxBound = maxVertexValue; if (numBits > MAX_SUPPORTED_BITS) { throw new IllegalArgumentException( "Tile coordinate span " + this.tileExtent + " is too large"); } } protected void validateCoordinates(Vertex vertex) { // TODO: also check of int overflow as we limiting the sfc ids to max int size if (vertex.x() < minBound || vertex.y() < minBound || vertex.x() > maxBound || vertex.y() > maxBound) { throw new IllegalArgumentException( "The specified tile buffer size is currently not supported."); } } private static int getCoordinateShift(int minVertexValue) { if (minVertexValue == Integer.MIN_VALUE) { return Integer.MAX_VALUE; } return (minVertexValue < 0) ? Math.abs(minVertexValue) : 0; } public abstract int encode(Vertex vertex); public abstract int[] decode(int mortonCode); public int numBits() { return this.numBits; } public int coordinateShift() { return this.coordinateShift; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/geometry/Vertex.java ================================================ package org.maplibre.mlt.converter.geometry; public record Vertex(int x, int y) {} ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/geometry/ZOrderCurve.java ================================================ package org.maplibre.mlt.converter.geometry; public class ZOrderCurve extends SpaceFillingCurve { public ZOrderCurve(int minVertexValue, int maxVertexValue) { super(minVertexValue, maxVertexValue); } public int encode(Vertex vertex) { validateCoordinates(vertex); var shiftedX = vertex.x() + coordinateShift; var shiftedY = vertex.y() + coordinateShift; int mortonCode = 0; for (int i = 0; i < numBits; i++) { mortonCode |= (shiftedX & (1 << i)) << i | (shiftedY & (1 << i)) << (i + 1); } return mortonCode; } public int[] decode(int mortonCode) { int x = decodeMorton(mortonCode) - coordinateShift; int y = decodeMorton(mortonCode >> 1) - coordinateShift; return new int[] {x, y}; } private int decodeMorton(int code) { int coordinate = 0; for (int i = 0; i < numBits; i++) { coordinate |= (int) ((code & (1L << (2 * i))) >> i); } return coordinate; } public static int[] decode(int mortonCode, int numBits, int coordinateShift) { int x = decodeMorton(mortonCode, numBits) - coordinateShift; int y = decodeMorton(mortonCode >> 1, numBits) - coordinateShift; return new int[] {x, y}; } private static int decodeMorton(int code, int numBits) { int coordinate = 0; for (int i = 0; i < numBits; i++) { coordinate |= (int) ((code & (1L << (2 * i))) >> i); } return coordinate; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/mvt/MvtUtils.java ================================================ package org.maplibre.mlt.converter.mvt; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import no.ecc.vectortile.VectorTileDecoder; import org.maplibre.mlt.data.Feature; import org.maplibre.mlt.data.Layer; import org.maplibre.mlt.data.MVTFeature; import org.maplibre.mlt.data.MapboxVectorTile; import org.springmeyer.Pbf; import org.springmeyer.VectorTile; import org.springmeyer.VectorTileLayer; public class MvtUtils { /* Uses the java-vector-tile library for decoding the MVT tile */ public static MapboxVectorTile decodeMvt(Path mvtFilePath) throws IOException { var mvt = Files.readAllBytes(mvtFilePath); return decodeMvt(mvt); } /* Uses the java-vector-tile library for decoding the MVT tile */ public static List decodeMvtFast(byte[] mvtTile) throws IOException { VectorTileDecoder mvtDecoder = new VectorTileDecoder(); mvtDecoder.setAutoScale(false); var decodedTile = mvtDecoder.decode(mvtTile); return decodedTile.asList(); } /* Use the java port of the vector-tile-js library created by Dane Springmeyer. * To get realistic number and have a fair comparison in terms of the decoding performance, * the geometries of the features have to be decoded. * */ public static Map decodeMvtMapbox(byte[] mvtTile) throws IOException { Pbf pbf = new Pbf(mvtTile); VectorTile vectorTile = new VectorTile(pbf, pbf.length); for (var layer : vectorTile.layers.values()) { for (int i = 0; i < layer.length; i++) { var feature = layer.feature(i); var ignored = feature.loadGeometry(); } } return vectorTile.layers; } public static MapboxVectorTile decodeMvt(byte[] mvtTile) throws IOException { VectorTileDecoder mvtDecoder = new VectorTileDecoder(); mvtDecoder.setAutoScale(false); final var mvtFeatures = mvtDecoder.decode(mvtTile); final var featuresByLayer = StreamSupport.stream(mvtFeatures.spliterator(), false) .collect( Collectors.groupingBy( f -> f.getLayerName(), LinkedHashMap::new, Collectors.toList())); var layers = new ArrayList(featuresByLayer.size()); for (var layerGroup : featuresByLayer.entrySet()) { final var layerName = layerGroup.getKey(); final var layerFeatures = layerGroup.getValue(); var tileExtent = 0; final var features = new ArrayList(layerFeatures.size()); final var builder = MVTFeature.builder(); for (var mvtFeature : layerFeatures) { features.add( builder .index(features.size()) .id(mvtFeature.getId()) .geometry(mvtFeature.getGeometry()) .properties(mvtFeature.getAttributes()) .build()); tileExtent = Math.max(tileExtent, mvtFeature.getExtent()); } layers.add(new Layer(layerName, features, tileExtent)); } return new MapboxVectorTile(layers); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/tessellation/TessellatedPolygon.java ================================================ package org.maplibre.mlt.converter.tessellation; import java.util.List; public record TessellatedPolygon(List indexBuffer, int numTriangles, int numVertices) {} ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/converter/tessellation/TessellationUtils.java ================================================ package org.maplibre.mlt.converter.tessellation; import com.google.gson.Gson; import com.google.gson.JsonObject; import jakarta.annotation.Nullable; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; import org.jetbrains.annotations.NotNull; import org.locationtech.jts.geom.MultiPolygon; import org.locationtech.jts.geom.Polygon; import org.maplibre.earcut4j.Earcut; public class TessellationUtils { private TessellationUtils() {} public static TessellatedPolygon tessellatePolygon( Polygon polygon, int indexOffset, @Nullable URI tessellateSource) { final var flattenedCoordinates = flatCoordinatesWithoutClosingPoint(polygon); final var holeIndices = new ArrayList(); var numVertices = polygon.getExteriorRing().getCoordinates().length - 1; for (int i = 0; i < polygon.getNumInteriorRing(); i++) { holeIndices.add(numVertices); numVertices += polygon.getInteriorRingN(i).getCoordinates().length - 1; } final var holeIndicesArray = !holeIndices.isEmpty() ? holeIndices.stream().mapToInt(i -> i).toArray() : null; Stream indices; if (tessellateSource != null) { indices = Arrays.stream( tessellatePolygonRemote(flattenedCoordinates, holeIndicesArray, tessellateSource)) .boxed(); } else { indices = Earcut.earcut(flattenedCoordinates, holeIndicesArray, 2).stream(); } final var indexList = indices.map(index -> index + indexOffset).toList(); final var numTriangles = indexList.size() / 3; return new TessellatedPolygon(indexList, numTriangles, numVertices); } private static int[] tessellatePolygonRemote( double[] flattenedCoordinates, int[] holeIndicesArray, @NotNull URI tessellateSource) { try { HttpURLConnection conn = (HttpURLConnection) tessellateSource.toURL().openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/json"); conn.setRequestProperty("Accept", "application/json"); conn.setDoOutput(true); JsonObject requestBody = new JsonObject(); requestBody.add("vertices", new Gson().toJsonTree(flattenedCoordinates)); requestBody.add("holes", new Gson().toJsonTree(holeIndicesArray)); try (OutputStream os = conn.getOutputStream()) { byte[] input = requestBody.toString().getBytes(StandardCharsets.UTF_8); os.write(input, 0, input.length); } try (BufferedReader br = new BufferedReader( new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) { StringBuilder response = new StringBuilder(); String responseLine; while ((responseLine = br.readLine()) != null) { response.append(responseLine.trim()); } Gson gson = new Gson(); JsonObject jsonResponse = gson.fromJson(response.toString(), JsonObject.class); return gson.fromJson(jsonResponse.get("indices"), int[].class); } } catch (Exception e) { throw new RuntimeException(e); } } public static TessellatedPolygon tessellateMultiPolygon( MultiPolygon multiPolygon, @Nullable URI tessellateSource) { List indexBuffer = new ArrayList<>(); var numTriangles = 0; var numVertices = 0; /* The range of the values of the indices are created per MultiPolygon, * which means the min index of every new MultiPolygon is 0. * Because of the filtering happening on the map renderer side the indices have * to be adjusted with an offset. * */ for (int i = 0; i < multiPolygon.getNumGeometries(); i++) { var polygon = (Polygon) multiPolygon.getGeometryN(i); var tessellatedPolygon = tessellatePolygon(polygon, numVertices, tessellateSource); indexBuffer.addAll(tessellatedPolygon.indexBuffer()); numTriangles += tessellatedPolygon.numTriangles(); // indexOffset = tessellatedPolygon.indexBuffer().stream().max(Integer::compareTo).get() + 1; numVertices += tessellatedPolygon.numVertices(); } return new TessellatedPolygon(indexBuffer, numTriangles, numVertices); } private static double[] flatCoordinatesWithoutClosingPoint(Polygon polygon) { var shell = polygon.getExteriorRing(); var shellCoordinates = shell.getCoordinates(); var coordinates = new ArrayList<>(Arrays.asList(shellCoordinates).subList(0, shellCoordinates.length - 1)); for (var i = 0; i < polygon.getNumInteriorRing(); ++i) { var hole = polygon.getInteriorRingN(i); var childCoordinates = hole.getCoordinates(); coordinates.addAll(Arrays.asList(childCoordinates).subList(0, childCoordinates.length - 1)); } return coordinates.stream() .flatMapToDouble(c -> Arrays.stream(new double[] {c.x, c.y})) .toArray(); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/data/Feature.java ================================================ package org.maplibre.mlt.data; import jakarta.annotation.Nullable; import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Stream; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.experimental.Accessors; import lombok.experimental.SuperBuilder; import org.jetbrains.annotations.NotNull; import org.locationtech.jts.geom.Geometry; import org.maplibre.mlt.metadata.tileset.MltMetadata; /** A base class for features */ @SuperBuilder(toBuilder = true) @EqualsAndHashCode public abstract class Feature implements FeatureInterface { @Getter protected final int index; @Accessors(fluent = true) @Getter @Builder.Default protected final boolean hasId = false; @Getter @Builder.Default long id = 0; @Getter @Builder.Default @Nullable protected final Geometry geometry = null; public Iterable getProperties() { return () -> getPropertyStream().iterator(); } public Stream getPropertyStream() { return getPropertyStream(false); } public Optional findProperty(@NotNull Predicate predicate) { return getPropertyStream().filter(predicate).findFirst(); } public Optional findProperty(@NotNull String name) { return findProperty(p -> p.getName().equals(name)); } public Optional findProperty( @NotNull String name, @NotNull MltMetadata.ScalarType type) { return findProperty(name) .filter( p -> !p.isNested() && p.getType().scalarType() != null && p.getType().scalarType().physicalType().equals(type)); } /** * Returns the ID as a boxed Long, or null if the feature has no ID. Use this where a nullable * Long is required, e.g., intermediate lists, serialization. Prefer {@link #hasId()} and {@link * #getId()} in hot paths. */ @Nullable public Long idOrNull() { return hasId() ? getId() : null; } public abstract static class FeatureBuilder> { public B id(long id) { this.id$value = id; this.id$set = true; return hasId(true); } public B id(@Nullable Long id) { return (id != null) ? id(id.longValue()) : hasId(false); } } public abstract FeatureBuilder toBuilder(); } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/data/FeatureInterface.java ================================================ package org.maplibre.mlt.data; import jakarta.annotation.Nullable; import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Stream; import org.jetbrains.annotations.NotNull; import org.locationtech.jts.geom.Geometry; import org.maplibre.mlt.metadata.tileset.MltMetadata; public interface FeatureInterface { boolean hasId(); long getId(); @NotNull Geometry getGeometry(); Iterable getProperties(); Stream getPropertyStream(); Stream getPropertyStream(boolean parallel); Optional findProperty(@NotNull Predicate predicate); Optional findProperty(@NotNull String name); Optional findProperty(@NotNull String name, @NotNull MltMetadata.ScalarType type); /** * Returns the ID as a boxed Long, or null if the feature has no ID. Use this where a nullable * Long is required, e.g., intermediate lists, serialization. Prefer {@link #hasId()} and {@link * #getId()} in hot paths. */ @Nullable Long idOrNull(); } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/data/IndexedProperty.java ================================================ package org.maplibre.mlt.data; import java.util.ArrayList; import java.util.SequencedCollection; import org.maplibre.mlt.metadata.tileset.MltMetadata; public class IndexedProperty extends Property { private final SequencedCollection values; public IndexedProperty( MltMetadata.FieldType type, String name, SequencedCollection values) { super(type, name, null); this.values = values; } @Override public Object getValue(int featureIndex) { if (featureIndex < 0 || featureIndex >= values.size()) { throw new IndexOutOfBoundsException( "Index " + featureIndex + " is out of bounds for values of size " + values.size()); } if (values instanceof ArrayList arrayList) { return arrayList.get(featureIndex); } return values.stream().skip(featureIndex).findFirst().orElse(null); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/data/Layer.java ================================================ package org.maplibre.mlt.data; import java.util.SequencedCollection; import org.jetbrains.annotations.NotNull; public record Layer( @NotNull String name, @NotNull SequencedCollection features, int tileExtent) {} ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/data/LayerSource.java ================================================ package org.maplibre.mlt.data; import java.util.stream.Stream; import org.jetbrains.annotations.NotNull; public interface LayerSource { default long getLayerCount() { return getLayerStream().count(); } @NotNull default Iterable getLayers() { return () -> getLayerStream().iterator(); } @NotNull default Stream getLayerStream() { return getLayerStream(false); } @NotNull Stream getLayerStream(boolean parallel); } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/data/MLTFeature.java ================================================ package org.maplibre.mlt.data; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.experimental.SuperBuilder; import org.jetbrains.annotations.NotNull; import org.maplibre.mlt.metadata.tileset.MltMetadata; /** An MLT Feature, which may have nested properties that are not supported in MVT. */ @SuperBuilder(toBuilder = true) @EqualsAndHashCode(callSuper = true) public class MLTFeature extends Feature { @NotNull @Builder.Default private final Map properties = Map.of(); @Override public Optional findProperty(String name) { return Optional.ofNullable(properties.get(name)); } @Override public Stream getPropertyStream(boolean parallel) { final var entries = properties.entrySet(); return (parallel ? entries.parallelStream() : entries.stream()).map(Map.Entry::getValue); } static Property adapt(String name, Object value) { return new Property(getType(value), name, value); } static Property adapt(Map.Entry entry) { return adapt(entry.getKey(), entry.getValue()); } static MltMetadata.FieldType getType(Object value) { if (value instanceof Map) { return MltMetadata.complexFieldType(MltMetadata.ComplexType.MAP, true); } final var isNullable = true; return MltMetadata.scalarFieldType(MVTFeature.getType(value), isNullable); } /** * Builder class for #MLTFeature * * @param The final builder type, for covariance in property setters. * @param The final feature type, for covariance in the build() method. */ public abstract static class MLTFeatureBuilder< C extends MLTFeature, B extends MLTFeatureBuilder> extends Feature.FeatureBuilder { /** * Set properties from a Map of raw values, which will be adapted to Property objects. * * @param rawProperties A Map of String keys to Object values * @return The builder instance, for chaining */ public B rawProperties(Map rawProperties) { return properties( rawProperties.entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, MLTFeature::adapt))); } } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/data/MVTFeature.java ================================================ package org.maplibre.mlt.data; import java.math.BigInteger; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.experimental.SuperBuilder; import org.jetbrains.annotations.NotNull; import org.maplibre.mlt.data.unsigned.U32; import org.maplibre.mlt.data.unsigned.U64; import org.maplibre.mlt.data.unsigned.U8; import org.maplibre.mlt.metadata.tileset.MltMetadata; @SuperBuilder(toBuilder = true) @EqualsAndHashCode(callSuper = true) public class MVTFeature extends Feature { @NotNull @Builder.Default private final Map properties = Map.of(); public Map getRawProperties() { return properties; } @Override public Optional findProperty(String name) { return Optional.ofNullable(properties.get(name)).map(value -> adapt(name, value)); } @Override public Stream getPropertyStream(boolean parallel) { final var entries = properties.entrySet(); return (parallel ? entries.parallelStream() : entries.stream()).map(MVTFeature::adapt); } static Property adapt(String name, Object value) { final var type = getType(value); final var isNullable = true; return new Property(MltMetadata.scalarFieldType(type, isNullable), name, value); } static Property adapt(Map.Entry entry) { return adapt(entry.getKey(), entry.getValue()); } static MltMetadata.ScalarType getType(Object value) { return switch (value) { case null -> MltMetadata.ScalarType.UNRECOGNIZED; case String ignored -> MltMetadata.ScalarType.STRING; case Boolean ignored -> MltMetadata.ScalarType.BOOLEAN; case Double ignored -> MltMetadata.ScalarType.DOUBLE; case Float ignored -> MltMetadata.ScalarType.FLOAT; case U8 ignored -> MltMetadata.ScalarType.UINT_8; case Integer ignored -> MltMetadata.ScalarType.INT_32; case U32 ignored -> MltMetadata.ScalarType.UINT_32; case Long l -> (l.intValue() == l) ? MltMetadata.ScalarType.INT_32 : MltMetadata.ScalarType.INT_64; case U64 ignored -> MltMetadata.ScalarType.UINT_64; case BigInteger i -> (i.intValue() == i.longValue()) ? MltMetadata.ScalarType.INT_32 : MltMetadata.ScalarType.INT_64; default -> throw new IllegalArgumentException( "Unsupported property value type: " + value.getClass()); }; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/data/MapLibreTile.java ================================================ package org.maplibre.mlt.data; import java.util.Collection; import java.util.stream.Stream; import org.jetbrains.annotations.NotNull; public class MapLibreTile implements LayerSource { @NotNull private final Collection layers; public MapLibreTile(@NotNull Collection layers) { this.layers = layers; } @Override public @NotNull Stream getLayerStream(boolean parallel) { return parallel ? layers.parallelStream() : layers.stream(); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/data/MapboxVectorTile.java ================================================ package org.maplibre.mlt.data; import jakarta.annotation.Nullable; import java.util.SequencedCollection; import java.util.stream.Stream; import org.apache.commons.lang3.tuple.Triple; import org.jetbrains.annotations.NotNull; public class MapboxVectorTile implements LayerSource { private @NotNull SequencedCollection layers; private @Nullable Triple tileId; public MapboxVectorTile(@NotNull SequencedCollection layers) { this.layers = layers; } public MapboxVectorTile( @NotNull SequencedCollection layers, @Nullable Triple tileId) { this(layers); this.tileId = tileId; } public void setTileId(@Nullable Triple tileId) { this.tileId = tileId; } public @Nullable Triple tileId() { return tileId; } @Override public long getLayerCount() { return layers.size(); } @Override public @NotNull Stream getLayerStream(boolean parallel) { return parallel ? layers.parallelStream() : layers.stream(); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/data/Property.java ================================================ package org.maplibre.mlt.data; import java.util.Objects; import lombok.Getter; import org.jetbrains.annotations.NotNull; import org.maplibre.mlt.metadata.tileset.MltMetadata; /** Represents a property of a feature */ public class Property { @Getter @NotNull private final MltMetadata.FieldType type; @Getter private final String name; private final Object value; /** * Create directly from a complex type * * @param type The type of the property * @param name The name of the property * @param value The value of the property, which should be compatible with the type. No validation * is performed. */ public Property(@NotNull MltMetadata.FieldType type, String name, Object value) { this.type = Objects.requireNonNull(type); this.name = name; this.value = value; if (!isNested(type) && value != null) { if (type.scalarType() == null || type.scalarType().physicalType().ordinal() < MltMetadata.ScalarType.BOOLEAN.ordinal() || type.scalarType().physicalType().ordinal() > MltMetadata.ScalarType.STRING.ordinal()) { throw new IllegalArgumentException( "FieldType must have either a valid scalarType or a complexType of MAP"); } } } /** * @param featureIndex The index of the feature for which to get the value * @return The value of the property. */ public Object getValue(int featureIndex) { return value; } private static boolean isNested(MltMetadata.FieldType type) { return (type.complexType() != null && type.complexType().physicalType() == MltMetadata.ComplexType.MAP); } /** * @return True if the property is nested */ public boolean isNested() { return isNested(type); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/data/unsigned/U32.java ================================================ package org.maplibre.mlt.data.unsigned; public record U32(int value) implements Unsigned { public static U32 of(long value) { if (value < 0 || value > 0xFFFFFFFFL) { throw new IllegalArgumentException("Out of range for u32"); } return new U32((int) value); } @Override public Byte byteValue() { final var v = value; if ((byte) v == v) { return (byte) v; } return null; } @Override public Integer intValue() { return value; } @Override public Long longValue() { return Integer.toUnsignedLong(value); } @Override public String toString() { return "u32(" + Integer.toUnsignedLong(value) + ")"; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/data/unsigned/U64.java ================================================ package org.maplibre.mlt.data.unsigned; import java.math.BigInteger; public record U64(long value) implements Unsigned { public static U64 of(BigInteger value) { if (value.signum() < 0 || value.bitLength() > 64) { throw new IllegalArgumentException("Out of range for u64"); } return new U64(value.longValue()); } @Override public Byte byteValue() { final var v = value; if ((byte) v == v) { return (byte) v; } return null; } @Override public Integer intValue() { final var v = value; if ((int) v == v) { return (int) v; } return null; } @Override public Long longValue() { return value; } @Override public String toString() { return "u64(" + Long.toUnsignedString(value) + ")"; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/data/unsigned/U8.java ================================================ package org.maplibre.mlt.data.unsigned; public record U8(byte value) implements Unsigned { public static U8 of(int value) { if (value < 0 || value > 255) { throw new IllegalArgumentException("Out of range for u8"); } return new U8((byte) value); } @Override public Byte byteValue() { return value; } @Override public Integer intValue() { return Byte.toUnsignedInt(value); } @Override public Long longValue() { return Byte.toUnsignedLong(value); } @Override public String toString() { return "u8(" + Byte.toUnsignedInt(value) + ")"; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/data/unsigned/Unsigned.java ================================================ package org.maplibre.mlt.data.unsigned; /** * Represents an unsigned integer of a fixed bit width (8, 32, or 64 bits). * *

This interface abstracts over different unsigned integer types in Java, which do not natively * support unsigned primitives, providing a common API for working with unsigned values regardless * of their underlying representation. */ public sealed interface Unsigned permits U8, U32, U64 { Byte byteValue(); Integer intValue(); Long longValue(); @Override String toString(); } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/decoder/ByteRleDecoder.java ================================================ package org.maplibre.mlt.decoder; import java.nio.ByteBuffer; /** * Decodes byte run-length encoded data. * *

The encoding format uses a control byte followed by data: * *

    *
  • Control byte 0x00-0x7F: Run of (control + 3) copies of the next byte *
  • Control byte 0x80-0xFF: (256 - control) literal bytes follow *
*/ public class ByteRleDecoder { private static final int MIN_REPEAT_SIZE = 3; private final ByteBuffer buffer; private final byte[] literals = new byte[128]; private int numLiterals = 0; private int used = 0; private boolean repeat = false; public ByteRleDecoder(byte[] data, int offset, int length) { this.buffer = ByteBuffer.wrap(data, offset, length); } private void readValues() { int control = buffer.get() & 0xFF; if (control < 0x80) { // Run: control + MIN_REPEAT_SIZE copies of the next byte repeat = true; numLiterals = control + MIN_REPEAT_SIZE; literals[0] = buffer.get(); } else { // Literals: 256 - control literal bytes repeat = false; numLiterals = 256 - control; buffer.get(literals, 0, numLiterals); } used = 0; } public byte next() { if (used == numLiterals) { readValues(); } if (repeat) { used++; return literals[0]; } else { return literals[used++]; } } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/decoder/DecodingUtils.java ================================================ package org.maplibre.mlt.decoder; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.BitSet; import me.lemire.integercompression.Composition; import me.lemire.integercompression.FastPFOR; import me.lemire.integercompression.IntWrapper; import me.lemire.integercompression.IntegerCODEC; import me.lemire.integercompression.VariableByte; import org.apache.commons.lang3.tuple.Pair; public class DecodingUtils { private DecodingUtils() {} // TODO: quick and dirty -> optimize for performance public static int[] decodeVarints(byte[] src, IntWrapper pos, int numValues) throws IOException { var values = new int[numValues]; var dstOffset = 0; for (var i = 0; i < numValues; i++) { var offset = decodeVarint(src, pos.get(), values, dstOffset); dstOffset++; pos.set(offset); } return values; } public static long[] decodeLongVarints(byte[] src, IntWrapper pos, int numValues) { var values = new long[numValues]; for (var i = 0; i < numValues; i++) { var value = decodeLongVarint(src, pos); values[i] = value; } return values; } public static long decodeLongVarint(byte[] bytes, IntWrapper pos) { // TODO: write faster decoding method for varint long value = 0; int shift = 0; int index = pos.get(); while (index < bytes.length) { byte b = bytes[index++]; value |= (long) (b & 0x7F) << shift; if ((b & 0x80) == 0) { break; } shift += 7; if (shift > 63) { throw new IllegalArgumentException("Varint too long"); } } pos.set(index); return value; } // Source: // https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/util/VarInt.java /** * Reads a varint from src, places its values into the first element of dst and returns the offset * in to src of the first byte after the varint. * * @param src source buffer to retrieve from * @param srcOffset offset within src * @param dst the resulting int values * @param dstOffset offset with dst * @return the updated offset after reading the varint */ private static int decodeVarint(byte[] src, int srcOffset, int[] dst, int dstOffset) throws IOException { try (var stream = new ByteArrayInputStream(src, srcOffset, src.length - srcOffset)) { final var result = decodeVarintWithLength(stream); dst[dstOffset] = result.getLeft(); return srcOffset + result.getRight(); } } public static Pair decodeVarintWithLength(InputStream stream) throws IOException { var b = (byte) stream.read(); var bytesRead = 1; int value = b & 0x7f; if ((b & 0x80) != 0) { b = (byte) stream.read(); bytesRead++; value |= (b & 0x7f) << 7; if ((b & 0x80) != 0) { b = (byte) stream.read(); bytesRead++; value |= (b & 0x7f) << 14; if ((b & 0x80) != 0) { b = (byte) stream.read(); bytesRead++; value |= (b & 0x7f) << 21; if ((b & 0x80) != 0) { b = (byte) stream.read(); bytesRead++; value |= (b & 0x7f) << 28; if ((b & 0x80) != 0 || 15 < b) { throw new IOException("Varint overflow"); } } } } } return Pair.of(value, bytesRead); } public static int decodeVarint(InputStream stream) throws IOException { return decodeVarintWithLength(stream).getLeft(); } public static String decodeString(InputStream stream) throws IOException { var length = decodeVarint(stream); return new String(stream.readNBytes(length), StandardCharsets.UTF_8); } public static int decodeZigZag(int encoded) { return (encoded >>> 1) ^ (-(encoded & 1)); } public static long decodeZigZag(long encoded) { return (encoded >>> 1) ^ (-(encoded & 1)); } public static int[] decodeFastPfor( byte[] encodedValues, int numValues, int byteLength, IntWrapper pos) { var encodedValuesSlice = Arrays.copyOfRange(encodedValues, pos.get(), pos.get() + byteLength); // TODO: get rid of that conversion IntBuffer intBuf = ByteBuffer.wrap(encodedValuesSlice) // TODO: change to little endian .order(ByteOrder.BIG_ENDIAN) .asIntBuffer(); int[] intValues = new int[(int) Math.ceil(byteLength / 4d)]; for (var i = 0; i < intValues.length; i++) { intValues[i] = intBuf.get(i); } int[] decodedValues = new int[numValues]; var inputOffset = new IntWrapper(0); var outputOffset = new IntWrapper(0); IntegerCODEC ic = new Composition(new FastPFOR(), new VariableByte()); ic.uncompress(intValues, inputOffset, intValues.length, decodedValues, outputOffset); pos.add(byteLength); return decodedValues; } public static int[] decodeFastPforDeltaCoordinates( byte[] encodedValues, int numValues, int byteLength, IntWrapper pos) { var encodedValuesSlice = Arrays.copyOfRange(encodedValues, pos.get(), pos.get() + byteLength); // TODO: get rid of that conversion IntBuffer intBuf = ByteBuffer.wrap(encodedValuesSlice) // TODO: change to little endian .order(ByteOrder.BIG_ENDIAN) .asIntBuffer(); int[] intValues = new int[(int) Math.ceil(byteLength / 4d)]; for (var i = 0; i < intValues.length; i++) { intValues[i] = intBuf.get(i); } int[] decompressedValues = new int[numValues]; var inputOffset = new IntWrapper(0); var outputOffset = new IntWrapper(0); IntegerCODEC ic = new Composition(new FastPFOR(), new VariableByte()); ic.uncompress(intValues, inputOffset, intValues.length, decompressedValues, outputOffset); var decodedValues = new int[numValues]; for (var i = 0; i < numValues; i++) { var zigZagValue = decompressedValues[i]; decodedValues[i] = (zigZagValue >>> 1) ^ (-(zigZagValue & 1)); } pos.set(pos.get() + byteLength); var values = new int[numValues]; var previousValueX = 0; var previousValueY = 0; for (var i = 0; i < numValues; i += 2) { var deltaX = decodedValues[i]; var deltaY = decodedValues[i + 1]; var x = previousValueX + deltaX; var y = previousValueY + deltaY; values[i] = x; values[i + 1] = y; previousValueX = x; previousValueY = y; } return values; } public static byte[] decodeByteRle(byte[] buffer, int numBytes, int byteSize, IntWrapper pos) { var reader = new ByteRleDecoder(buffer, pos.get(), byteSize); var values = new byte[numBytes]; for (var i = 0; i < numBytes; i++) { values[i] = reader.next(); } pos.add(byteSize); return values; } public static BitSet decodeBooleanRle( byte[] buffer, int numBooleans, int byteSize, IntWrapper pos) { var numBytes = (int) Math.ceil(numBooleans / 8d); var byteStream = decodeByteRle(buffer, numBytes, byteSize, pos); // TODO: get rid of that conversion return BitSet.valueOf(byteStream); } public static int[] decodeUnsignedRLE(int[] data, int numRuns, int numTotalValues) { var values = new int[numTotalValues]; var offset = 0; for (var i = 0; i < numRuns; i++) { var runLength = data[i]; var value = data[i + numRuns]; for (var j = offset; j < offset + runLength; j++) { values[j] = value; } offset += runLength; } return values; } public static long[] decodeUnsignedRLE(long[] data, int numRuns, int numTotalValues) { var values = new long[numTotalValues]; var offset = 0L; for (var i = 0; i < numRuns; i++) { var runLength = data[i]; var value = data[i + numRuns]; for (var j = offset; j < offset + runLength; j++) { values[(int) j] = value; } offset += runLength; } return values; } public static float[] decodeFloatsLE(byte[] encodedValues, IntWrapper pos, int numValues) { var fb = ByteBuffer.wrap(encodedValues, pos.get(), numValues * Float.BYTES) .order(ByteOrder.LITTLE_ENDIAN) .asFloatBuffer(); pos.set(pos.get() + numValues * Float.BYTES); var decodedValues = new float[fb.limit()]; fb.get(decodedValues); return decodedValues; } public static double[] decodeDoublesLE(byte[] encodedValues, IntWrapper pos, int numValues) { var fb = ByteBuffer.wrap(encodedValues, pos.get(), numValues * Double.BYTES) .order(ByteOrder.LITTLE_ENDIAN) .asDoubleBuffer(); pos.set(pos.get() + numValues * Double.BYTES); var decodedValues = new double[fb.limit()]; fb.get(decodedValues); return decodedValues; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/decoder/DoubleDecoder.java ================================================ package org.maplibre.mlt.decoder; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; import me.lemire.integercompression.IntWrapper; import org.maplibre.mlt.metadata.stream.StreamMetadata; public class DoubleDecoder { private DoubleDecoder() {} public static List decodeDoubleStream( byte[] data, IntWrapper offset, StreamMetadata streamMetadata) { if ((long) streamMetadata.numValues() * Double.BYTES == streamMetadata.byteLength()) { final var values = DecodingUtils.decodeDoublesLE(data, offset, streamMetadata.numValues()); return Arrays.stream(values).boxed().collect(Collectors.toUnmodifiableList()); } else { // Compatibility with tilesets encoded before double support was added final var values = DecodingUtils.decodeFloatsLE(data, offset, streamMetadata.numValues()); return IntStream.range(0, values.length) .mapToDouble(i -> values[i]) .boxed() .collect(Collectors.toUnmodifiableList()); } } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/decoder/FloatDecoder.java ================================================ package org.maplibre.mlt.decoder; import java.util.ArrayList; import java.util.List; import me.lemire.integercompression.IntWrapper; import org.maplibre.mlt.metadata.stream.StreamMetadata; public class FloatDecoder { private FloatDecoder() {} public static List decodeFloatStream( byte[] data, IntWrapper offset, StreamMetadata streamMetadata) { var values = DecodingUtils.decodeFloatsLE(data, offset, streamMetadata.numValues()); var valuesList = new ArrayList(values.length); for (var value : values) { valuesList.add(value); } return valuesList; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/decoder/GeometryDecoder.java ================================================ package org.maplibre.mlt.decoder; import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import me.lemire.integercompression.IntWrapper; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.LinearRing; import org.locationtech.jts.geom.Point; import org.locationtech.jts.geom.Polygon; import org.maplibre.mlt.converter.MortonSettings; import org.maplibre.mlt.converter.geometry.GeometryType; import org.maplibre.mlt.converter.geometry.ZOrderCurve; import org.maplibre.mlt.metadata.stream.DictionaryType; import org.maplibre.mlt.metadata.stream.MortonEncodedStreamMetadata; import org.maplibre.mlt.metadata.stream.OffsetType; import org.maplibre.mlt.metadata.stream.PhysicalLevelTechnique; import org.maplibre.mlt.metadata.stream.StreamMetadataDecoder; public class GeometryDecoder { public record GeometryColumn( List geometryTypes, List numGeometries, List numParts, List numRings, List vertexOffsets, List vertexList, List triangles, List indexOffsets) {} private GeometryDecoder() {} public static GeometryColumn decodeGeometryColumn(byte[] tile, int numStreams, IntWrapper offset) throws IOException { var geometryTypeMetadata = StreamMetadataDecoder.decode(tile, offset); var geometryTypes = IntegerDecoder.decodeIntStream(tile, offset, geometryTypeMetadata, false); List numGeometries = null; List numParts = null; List numRings = null; List vertexOffsets = null; List indexOffsets = null; List vertexList = null; List triangles = null; for (var i = 0; i < numStreams - 1; i++) { var geometryStreamMetadata = StreamMetadataDecoder.decode(tile, offset); switch (geometryStreamMetadata.physicalStreamType()) { case LENGTH: switch (geometryStreamMetadata.logicalStreamType().lengthType()) { case GEOMETRIES: numGeometries = IntegerDecoder.decodeIntStream(tile, offset, geometryStreamMetadata, false); break; case PARTS: numParts = IntegerDecoder.decodeIntStream(tile, offset, geometryStreamMetadata, false); break; case RINGS: numRings = IntegerDecoder.decodeIntStream(tile, offset, geometryStreamMetadata, false); break; case TRIANGLES: triangles = IntegerDecoder.decodeIntStream(tile, offset, geometryStreamMetadata, false); break; } break; case OFFSET: { final var values = IntegerDecoder.decodeIntStream(tile, offset, geometryStreamMetadata, false); final var type = geometryStreamMetadata.logicalStreamType().offsetType(); if (type == OffsetType.VERTEX) { vertexOffsets = values; } else if (type == OffsetType.INDEX) { indexOffsets = values; } else { throw new RuntimeException("Unexpected offset stream " + type); } } break; case DATA: if (DictionaryType.VERTEX.equals( geometryStreamMetadata.logicalStreamType().dictionaryType())) { if (geometryStreamMetadata.physicalLevelTechnique() == PhysicalLevelTechnique.FAST_PFOR) { var vertexBuffer = DecodingUtils.decodeFastPforDeltaCoordinates( tile, geometryStreamMetadata.numValues(), geometryStreamMetadata.byteLength(), offset); vertexList = Arrays.stream(vertexBuffer).boxed().collect(Collectors.toList()); } else { vertexList = IntegerDecoder.decodeIntStream(tile, offset, geometryStreamMetadata, true); } } else { vertexList = IntegerDecoder.decodeMortonStream( tile, offset, (MortonEncodedStreamMetadata) geometryStreamMetadata); } break; } } return new GeometryColumn( geometryTypes, numGeometries, numParts, numRings, vertexOffsets, vertexList, triangles, indexOffsets); } public static Geometry[] decodeGeometry(GeometryColumn geometryColumn) { var geometries = new Geometry[geometryColumn.geometryTypes.size()]; var partOffsetCounter = 0; var ringOffsetsCounter = 0; var geometryOffsetsCounter = 0; var geometryCounter = 0; var geometryFactory = new GeometryFactory(); var vertexBufferOffset = 0; var vertexOffsetsOffset = 0; var geometryTypes = geometryColumn.geometryTypes(); var geometryOffsets = geometryColumn.numGeometries(); var partOffsets = geometryColumn.numParts(); var ringOffsets = geometryColumn.numRings(); var vertexOffsets = geometryColumn.vertexOffsets() != null ? geometryColumn.vertexOffsets().stream().mapToInt(i -> i).toArray() : null; var vertexBuffer = geometryColumn.vertexList.stream().mapToInt(i -> i).toArray(); final var containsPolygon = containsPolygon(geometryTypes); // TODO: refactor redundant code for (var geometryType : geometryTypes) { if (geometryType.equals(GeometryType.POINT.ordinal())) { if (vertexOffsets == null || vertexOffsets.length == 0) { var x = vertexBuffer[vertexBufferOffset++]; var y = vertexBuffer[vertexBufferOffset++]; var coordinate = new Coordinate(x, y); geometries[geometryCounter++] = geometryFactory.createPoint(coordinate); } else { var offset = vertexOffsets[vertexOffsetsOffset++] * 2; var x = vertexBuffer[offset]; var y = vertexBuffer[offset + 1]; var coordinate = new Coordinate(x, y); geometries[geometryCounter++] = geometryFactory.createPoint(coordinate); } } else if (geometryType.equals(GeometryType.MULTIPOINT.ordinal())) { var numPoints = geometryOffsets.get(geometryOffsetsCounter++); var points = new Point[numPoints]; if (vertexOffsets == null || vertexOffsets.length == 0) { for (var i = 0; i < numPoints; i++) { var x = vertexBuffer[vertexBufferOffset++]; var y = vertexBuffer[vertexBufferOffset++]; var coordinate = new Coordinate(x, y); points[i] = geometryFactory.createPoint(coordinate); } geometries[geometryCounter++] = geometryFactory.createMultiPoint(points); } else { for (var i = 0; i < numPoints; i++) { var offset = vertexOffsets[vertexOffsetsOffset++] * 2; var x = vertexBuffer[offset]; var y = vertexBuffer[offset + 1]; var coordinate = new Coordinate(x, y); points[i] = geometryFactory.createPoint(coordinate); } geometries[geometryCounter++] = geometryFactory.createMultiPoint(points); } } else if (geometryType.equals(GeometryType.LINESTRING.ordinal())) { var numVertices = containsPolygon ? ringOffsets.get(ringOffsetsCounter++) : partOffsets.get(partOffsetCounter++); if (vertexOffsets == null || vertexOffsets.length == 0) { var vertices = getLineString(vertexBuffer, vertexBufferOffset, numVertices, false); vertexBufferOffset += numVertices * 2; geometries[geometryCounter++] = geometryFactory.createLineString(vertices); } else { var vertices = decodeDictionaryEncodedLineString( vertexBuffer, vertexOffsets, vertexOffsetsOffset, numVertices, false); vertexOffsetsOffset += numVertices; geometries[geometryCounter++] = geometryFactory.createLineString(vertices); } } else if (geometryType.equals(GeometryType.POLYGON.ordinal())) { var numRings = partOffsets.get(partOffsetCounter++); var rings = new LinearRing[numRings - 1]; var numVertices = ringOffsets.get(ringOffsetsCounter++); if (vertexOffsets == null || vertexOffsets.length == 0) { LinearRing shell = getLinearRing(vertexBuffer, vertexBufferOffset, numVertices, geometryFactory); vertexBufferOffset += numVertices * 2; for (var i = 0; i < rings.length; i++) { numVertices = ringOffsets.get(ringOffsetsCounter++); rings[i] = getLinearRing(vertexBuffer, vertexBufferOffset, numVertices, geometryFactory); vertexBufferOffset += numVertices * 2; } geometries[geometryCounter++] = geometryFactory.createPolygon(shell, rings); } else { LinearRing shell = decodeDictionaryEncodedLinearRing( vertexBuffer, vertexOffsets, vertexOffsetsOffset, numVertices, geometryFactory); vertexOffsetsOffset += numVertices; for (var i = 0; i < rings.length; i++) { numVertices = ringOffsets.get(ringOffsetsCounter++); rings[i] = decodeDictionaryEncodedLinearRing( vertexBuffer, vertexOffsets, vertexOffsetsOffset, numVertices, geometryFactory); vertexOffsetsOffset += numVertices; } geometries[geometryCounter++] = geometryFactory.createPolygon(shell, rings); } } else if (geometryType.equals(GeometryType.MULTILINESTRING.ordinal())) { var numLineStrings = geometryOffsets.get(geometryOffsetsCounter++); var lineStrings = new LineString[numLineStrings]; if (vertexOffsets == null || vertexOffsets.length == 0) { for (var i = 0; i < numLineStrings; i++) { var numVertices = containsPolygon ? ringOffsets.get(ringOffsetsCounter++) : partOffsets.get(partOffsetCounter++); var vertices = getLineString(vertexBuffer, vertexBufferOffset, numVertices, false); lineStrings[i] = geometryFactory.createLineString(vertices); vertexBufferOffset += numVertices * 2; } geometries[geometryCounter++] = geometryFactory.createMultiLineString(lineStrings); } else { for (var i = 0; i < numLineStrings; i++) { var numVertices = containsPolygon ? ringOffsets.get(ringOffsetsCounter++) : partOffsets.get(partOffsetCounter++); var vertices = decodeDictionaryEncodedLineString( vertexBuffer, vertexOffsets, vertexOffsetsOffset, numVertices, false); lineStrings[i] = geometryFactory.createLineString(vertices); vertexOffsetsOffset += numVertices; } geometries[geometryCounter++] = geometryFactory.createMultiLineString(lineStrings); } } else if (geometryType.equals(GeometryType.MULTIPOLYGON.ordinal())) { var numPolygons = geometryOffsets.get(geometryOffsetsCounter++); var polygons = new Polygon[numPolygons]; var numVertices = 0; if (vertexOffsets == null || vertexOffsets.length == 0) { for (var i = 0; i < numPolygons; i++) { var numRings = partOffsets.get(partOffsetCounter++); var rings = new LinearRing[numRings - 1]; numVertices = ringOffsets.get(ringOffsetsCounter++); LinearRing shell = getLinearRing(vertexBuffer, vertexBufferOffset, numVertices, geometryFactory); vertexBufferOffset += numVertices * 2; for (var j = 0; j < rings.length; j++) { var numRingVertices = ringOffsets.get(ringOffsetsCounter++); rings[j] = getLinearRing(vertexBuffer, vertexBufferOffset, numRingVertices, geometryFactory); vertexBufferOffset += numRingVertices * 2; } polygons[i] = geometryFactory.createPolygon(shell, rings); } geometries[geometryCounter++] = geometryFactory.createMultiPolygon(polygons); } else { for (var i = 0; i < numPolygons; i++) { var numRings = partOffsets.get(partOffsetCounter++); var rings = new LinearRing[numRings - 1]; numVertices = ringOffsets.get(ringOffsetsCounter++); LinearRing shell = decodeDictionaryEncodedLinearRing( vertexBuffer, vertexOffsets, vertexOffsetsOffset, numVertices, geometryFactory); vertexOffsetsOffset += numVertices; for (var j = 0; j < rings.length; j++) { numVertices = ringOffsets.get(ringOffsetsCounter++); rings[j] = decodeDictionaryEncodedLinearRing( vertexBuffer, vertexOffsets, vertexOffsetsOffset, numVertices, geometryFactory); vertexOffsetsOffset += numVertices; } polygons[i] = geometryFactory.createPolygon(shell, rings); } geometries[geometryCounter++] = geometryFactory.createMultiPolygon(polygons); } } else { throw new IllegalArgumentException( "The specified geometry type is currently not supported: " + geometryType); } } return geometries; } private static LinearRing getLinearRing( int[] vertexBuffer, int startIndex, int numVertices, GeometryFactory geometryFactory) { var linearRing = getLineString(vertexBuffer, startIndex, numVertices, true); return geometryFactory.createLinearRing(linearRing); } private static LinearRing decodeDictionaryEncodedLinearRing( int[] vertexBuffer, int[] vertexOffsets, int vertexOffset, int numVertices, GeometryFactory geometryFactory) { var linearRing = decodeDictionaryEncodedLineString( vertexBuffer, vertexOffsets, vertexOffset, numVertices, true); return geometryFactory.createLinearRing(linearRing); } private static Coordinate[] getLineString( int[] vertexBuffer, int startIndex, int numVertices, boolean closeLineString) { var vertices = new Coordinate[closeLineString ? numVertices + 1 : numVertices]; for (var i = 0; i < numVertices * 2; i += 2) { var x = vertexBuffer[startIndex + i]; var y = vertexBuffer[startIndex + i + 1]; vertices[i / 2] = new Coordinate(x, y); } if (closeLineString) { vertices[vertices.length - 1] = vertices[0]; } return vertices; } private static Coordinate[] decodeDictionaryEncodedLineString( int[] vertexBuffer, int[] vertexOffsets, int vertexOffset, int numVertices, boolean closeLineString) { var vertices = new Coordinate[closeLineString ? numVertices + 1 : numVertices]; for (var i = 0; i < numVertices * 2; i += 2) { var offset = vertexOffsets[vertexOffset + i / 2] * 2; var x = vertexBuffer[offset]; var y = vertexBuffer[offset + 1]; vertices[i / 2] = new Coordinate(x, y); } if (closeLineString) { vertices[vertices.length - 1] = vertices[0]; } return vertices; } /* * The decoding of the Morton encoded vertices can happen completely in parallel on the GPU in the Vertex or Compute Shader. * Therefore, the decoding of the Morton encoded vertices is not part of the decoding benchmark from the storage into the * in-memory representation. * */ private static Coordinate[] decodeMortonDictionaryEncodedLineString( int[] vertexBuffer, int[] vertexOffsets, int vertexOffset, int numVertices, boolean closeLineString, MortonSettings mortonSettings) { var vertices = new Coordinate[closeLineString ? numVertices + 1 : numVertices]; for (var i = 0; i < numVertices; i++) { var offset = vertexOffsets[vertexOffset + i]; var mortonEncodedVertex = vertexBuffer[offset]; // TODO: refactor to use instance methods var vertex = ZOrderCurve.decode( mortonEncodedVertex, mortonSettings.numBits, mortonSettings.coordinateShift); vertices[i] = new Coordinate(vertex[0], vertex[1]); } if (closeLineString) { vertices[vertices.length - 1] = vertices[0]; } return vertices; } public static boolean containsPolygon(List geometryTypes) { return geometryTypes.stream() .anyMatch( geometryType -> geometryType == GeometryType.POLYGON.ordinal() || geometryType == GeometryType.MULTIPOLYGON.ordinal()); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/decoder/IntegerDecoder.java ================================================ package org.maplibre.mlt.decoder; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import me.lemire.integercompression.IntWrapper; import org.maplibre.mlt.decoder.vectorized.VectorizedDecodingUtils; import org.maplibre.mlt.metadata.stream.LogicalLevelTechnique; import org.maplibre.mlt.metadata.stream.MortonEncodedStreamMetadata; import org.maplibre.mlt.metadata.stream.PhysicalLevelTechnique; import org.maplibre.mlt.metadata.stream.RleEncodedStreamMetadata; import org.maplibre.mlt.metadata.stream.StreamMetadata; public class IntegerDecoder { private IntegerDecoder() {} public static List decodeMortonStream( byte[] data, IntWrapper offset, MortonEncodedStreamMetadata streamMetadata) throws IOException { int[] values; if (streamMetadata.physicalLevelTechnique() == PhysicalLevelTechnique.FAST_PFOR) { // TODO: numValues is not right if rle or delta rle is used -> add separate flag in // StreamMetadata values = DecodingUtils.decodeFastPfor( data, streamMetadata.numValues(), streamMetadata.byteLength(), offset); } else if (streamMetadata.physicalLevelTechnique() == PhysicalLevelTechnique.VARINT) { values = DecodingUtils.decodeVarints(data, offset, streamMetadata.numValues()); } else { throw new IllegalArgumentException( "Specified physical level technique not yet supported: " + streamMetadata.physicalLevelTechnique()); } return decodeMortonDelta(values, streamMetadata.numBits(), streamMetadata.coordinateShift()); } private static List decodeMortonDelta(int[] data, int numBits, int coordinateShift) { var vertices = new ArrayList(data.length * 2); var previousMortonCode = 0; for (var deltaCode : data) { var mortonCode = previousMortonCode + deltaCode; var vertex = decodeMortonCode(mortonCode, numBits, coordinateShift); vertices.add(vertex[0]); vertices.add(vertex[1]); previousMortonCode = mortonCode; } return vertices; } private static List decodeMortonCodes(int[] data, int numBits, int coordinateShift) { var vertices = new ArrayList(data.length * 2); for (var mortonCode : data) { var vertex = decodeMortonCode(mortonCode, numBits, coordinateShift); vertices.add(vertex[0]); vertices.add(vertex[1]); } return vertices; } private static int[] decodeMortonCode(int mortonCode, int numBits, int coordinateShift) { int x = decodeMorton(mortonCode, numBits) - coordinateShift; int y = decodeMorton(mortonCode >> 1, numBits) - coordinateShift; return new int[] {x, y}; } private static int decodeMorton(int code, int numBits) { int coordinate = 0; for (int i = 0; i < numBits; i++) { coordinate |= (code & (1 << (2 * i))) >> i; } return coordinate; } public static List decodeIntStream( byte[] data, IntWrapper offset, StreamMetadata streamMetadata, boolean isSigned) throws IOException { int[] values; if (streamMetadata.physicalLevelTechnique() == PhysicalLevelTechnique.FAST_PFOR) { values = DecodingUtils.decodeFastPfor( data, streamMetadata.numValues(), streamMetadata.byteLength(), offset); } else if (streamMetadata.physicalLevelTechnique() == PhysicalLevelTechnique.VARINT) { values = DecodingUtils.decodeVarints(data, offset, streamMetadata.numValues()); } else { throw new IllegalArgumentException( "Specified physical level technique not yet supported: " + streamMetadata.physicalLevelTechnique()); } return decodeIntArray(values, streamMetadata, isSigned); } private static List decodeIntArray( int[] values, StreamMetadata streamMetadata, boolean isSigned) throws IOException { switch (streamMetadata.logicalLevelTechnique1()) { case DELTA: if (streamMetadata.logicalLevelTechnique2().equals(LogicalLevelTechnique.RLE)) { var rleMetadata = (RleEncodedStreamMetadata) streamMetadata; values = DecodingUtils.decodeUnsignedRLE( values, rleMetadata.runs(), rleMetadata.numRleValues()); return decodeZigZagDelta(values); } return decodeZigZagDelta(values); case RLE: { var rleMetadata = (RleEncodedStreamMetadata) streamMetadata; var decodedValues = decodeRLE(values, rleMetadata.runs(), rleMetadata.numRleValues()); return isSigned ? decodeZigZag(decodedValues.stream().mapToInt(i -> i).toArray()) : decodedValues; } case NONE: { if (isSigned) { return decodeZigZag(values); } return Arrays.stream(values).boxed().collect(Collectors.toList()); } case COMPONENTWISE_DELTA: VectorizedDecodingUtils.decodeComponentwiseDeltaVec2(values); return Arrays.stream(values).boxed().collect(Collectors.toList()); case MORTON: // TODO: zig-zag decode when morton second logical level technique return decodeMortonCodes( values, ((MortonEncodedStreamMetadata) streamMetadata).numBits(), ((MortonEncodedStreamMetadata) streamMetadata).coordinateShift()); } throw new IllegalArgumentException( "The specified logical level technique is not supported for integers: " + streamMetadata.logicalLevelTechnique1()); } public static List decodeLongStream( byte[] data, IntWrapper offset, StreamMetadata streamMetadata, boolean isSigned) { var values = DecodingUtils.decodeLongVarints(data, offset, streamMetadata.numValues()); return decodeLongArray(values, streamMetadata, isSigned); } private static List decodeLongArray( long[] values, StreamMetadata streamMetadata, boolean isSigned) { switch (streamMetadata.logicalLevelTechnique1()) { case DELTA: if (streamMetadata.logicalLevelTechnique2().equals(LogicalLevelTechnique.RLE)) { var rleMetadata = (RleEncodedStreamMetadata) streamMetadata; values = DecodingUtils.decodeUnsignedRLE( values, rleMetadata.runs(), rleMetadata.numRleValues()); return decodeZigZagDelta(values); } return decodeZigZagDelta(values); case RLE: { var rleMetadata = (RleEncodedStreamMetadata) streamMetadata; var decodedValues = decodeRLE(values, rleMetadata.runs(), rleMetadata.numRleValues()); return isSigned ? decodeZigZag(decodedValues.stream().mapToLong(i -> i).toArray()) : decodedValues; } case NONE: { if (isSigned) { return decodeZigZag(values); } return Arrays.stream(values).boxed().collect(Collectors.toList()); } } throw new IllegalArgumentException( "The specified logical level technique is not supported for long integers: " + streamMetadata.logicalLevelTechnique1()); } // TODO: quick and dirty -> write fast vectorized solution private static List decodeRLE(int[] data, int numRuns, int numRleValues) { var values = new ArrayList(numRleValues); for (var i = 0; i < numRuns; i++) { var run = data[i]; var value = data[i + numRuns]; for (var j = 0; j < run; j++) { values.add(value); } } return values; } // TODO: quick and dirty -> write fast vectorized solution private static List decodeRLE(long[] data, int numRuns, int numRleValues) { var values = new ArrayList(numRleValues); for (var i = 0; i < numRuns; i++) { var run = data[i]; var value = data[i + numRuns]; for (var j = 0; j < run; j++) { values.add(value); } } return values; } private static List decodeZigZagDelta(int[] data) { var values = new ArrayList(data.length); var previousValue = 0; for (var zigZagDelta : data) { var delta = DecodingUtils.decodeZigZag(zigZagDelta); var value = previousValue + delta; values.add(value); previousValue = value; } return values; } private static List decodeZigZagDelta(long[] data) { var values = new ArrayList(data.length); var previousValue = 0L; for (var zigZagDelta : data) { var delta = DecodingUtils.decodeZigZag(zigZagDelta); var value = previousValue + delta; values.add(value); previousValue = value; } return values; } private static List decodeZigZag(long[] data) { var values = new ArrayList(data.length); for (var zigZagDelta : data) { var value = DecodingUtils.decodeZigZag(zigZagDelta); values.add(value); } return values; } private static List decodeZigZag(int[] data) { var values = new ArrayList(data.length); for (var zigZagDelta : data) { var value = DecodingUtils.decodeZigZag(zigZagDelta); values.add(value); } return values; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/decoder/MltDecoder.java ================================================ package org.maplibre.mlt.decoder; import com.google.common.io.CountingInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.BitSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.SequencedCollection; import java.util.stream.Collectors; import me.lemire.integercompression.IntWrapper; import org.apache.commons.lang3.tuple.Pair; import org.locationtech.jts.geom.Geometry; import org.maplibre.mlt.converter.encodings.MltTypeMap; import org.maplibre.mlt.data.Feature; import org.maplibre.mlt.data.IndexedProperty; import org.maplibre.mlt.data.Layer; import org.maplibre.mlt.data.MLTFeature; import org.maplibre.mlt.data.MapLibreTile; import org.maplibre.mlt.data.Property; import org.maplibre.mlt.metadata.stream.StreamMetadataDecoder; import org.maplibre.mlt.metadata.tileset.MltMetadata; public class MltDecoder { private MltDecoder() {} private static Layer parseBasicMVTEquivalent(int layerSize, InputStream stream) throws IOException { try (var countStream = new CountingInputStream(stream)) { final var metadataExtent = parseEmbeddedMetadata(countStream); final var metadata = metadataExtent.getLeft(); final var tileExtent = metadataExtent.getRight(); final var bodySize = layerSize - countStream.getCount(); return decodeMltLayer(countStream.readNBytes((int) bodySize), metadata, tileExtent); } } /** Decode an MLT tile with embedded metadata * */ public static MapLibreTile decodeMlTile(byte[] tileData) throws IOException { final var layers = new ArrayList(); try (final var stream = new ByteArrayInputStream(tileData)) { while (stream.available() > 0) { final var length = DecodingUtils.decodeVarint(stream); final var tag = DecodingUtils.decodeVarintWithLength(stream); final var bodySize = length - tag.getRight(); if (tag.getLeft() == 1) { final var layer = parseBasicMVTEquivalent(bodySize, stream); if (layer != null) { layers.add(layer); } } else { // Skip the remainder of this one stream.skip(length - tag.getRight()); } } } return new MapLibreTile(layers); } /** Decodes an MLT tile in a similar in-memory representation then MVT is using */ public static Layer decodeMltLayer( byte[] tile, MltMetadata.FeatureTable layerMetadata, int tileExtent) throws IOException { final var offset = new IntWrapper(0); List ids = null; Geometry[] geometries = null; final var properties = new ArrayList>(); for (var columnMetadata : layerMetadata.columns()) { final var columnName = columnMetadata.getName(); final var hasStreamCount = MltTypeMap.Tag0x01.hasStreamCount(columnMetadata); final var numStreams = hasStreamCount ? DecodingUtils.decodeVarints(tile, offset, 1)[0] : 0; // TODO: add decoding of vector type to be compliant with the spec // TODO: compare based on ids if (MltTypeMap.Tag0x01.isID(columnMetadata)) { BitSet presentStream = null; int presentStreamSize = 0; if (columnMetadata.isNullable()) { final var presentStreamMetadata = StreamMetadataDecoder.decode(tile, offset); presentStream = DecodingUtils.decodeBooleanRle( tile, presentStreamMetadata.numValues(), presentStreamMetadata.byteLength(), offset); presentStreamSize = presentStreamMetadata.numValues(); } final var idDataStreamMetadata = StreamMetadataDecoder.decode(tile, offset); List denseIds; if (columnMetadata.field().type().scalarType().hasLongId()) { denseIds = IntegerDecoder.decodeLongStream(tile, offset, idDataStreamMetadata, false); } else { denseIds = IntegerDecoder.decodeIntStream(tile, offset, idDataStreamMetadata, false).stream() .mapToLong(i -> i) .boxed() .collect(Collectors.toList()); } if (presentStream != null) { // Expand the dense (non-null only) ID list into a sparse list with nulls ids = new ArrayList<>(presentStreamSize); int denseIdx = 0; for (int i = 0; i < presentStreamSize; i++) { if (presentStream.get(i)) { ids.add(denseIds.get(denseIdx++)); } else { ids.add(null); } } } else { ids = denseIds; } } else if (MltTypeMap.Tag0x01.isGeometry(columnMetadata)) { assert hasStreamCount; final var geometryColumn = GeometryDecoder.decodeGeometryColumn(tile, numStreams, offset); geometries = GeometryDecoder.decodeGeometry(geometryColumn); } else { final var propertyColumn = PropertyDecoder.decodePropertyColumn(tile, offset, columnMetadata, numStreams); if (propertyColumn instanceof HashMap) { @SuppressWarnings("unchecked") var p = ((Map) propertyColumn); for (var a : p.entrySet()) { final var key = a.getKey(); if (a.getValue() instanceof ArrayList) { @SuppressWarnings("unchecked") final var list = (ArrayList) a.getValue(); sizeList(properties, list); final var prop = new IndexedProperty(columnMetadata.field().type(), key, list); for (int i = 0; i < list.size(); i++) { properties.get(i).merge(key, prop, MltDecoder::mergeFail); } } } } else if (propertyColumn instanceof SequencedCollection) { @SuppressWarnings("unchecked") final var list = (SequencedCollection) propertyColumn; sizeList(properties, list); final var prop = new IndexedProperty(columnMetadata.field().type(), columnName, list); for (int i = 0; i < list.size(); i++) { properties.get(i).merge(columnName, prop, MltDecoder::mergeFail); } } else { throw new RuntimeException("Unexpected property result"); } } } return (geometries != null) ? convertToLayer(ids, geometries, properties, layerMetadata, tileExtent) : null; } private static void sizeList( ArrayList> properties, SequencedCollection list) { if (properties.isEmpty()) { for (int i = 0; i < list.size(); i++) { properties.add(new HashMap<>()); } } else if (properties.size() != list.size()) { throw new RuntimeException("Feature count mismatch"); } } private static Layer convertToLayer( List ids, Geometry[] geometries, ArrayList> properties, MltMetadata.FeatureTable metadata, int tileExtent) { if (ids != null && geometries.length != ids.size()) { System.out.println( "Warning, in convertToLayer the size of ids(" + ids.size() + "), geometries(" + geometries.length + "), are not equal for layer: " + metadata.name()); } final var features = new ArrayList(geometries.length); final var builder = MLTFeature.builder(); for (var j = 0; j < geometries.length; j++) { features.add( builder .index(j) .id((ids != null) ? ids.get(j) : null) .geometry(geometries[j]) .properties(properties.isEmpty() ? Map.of() : properties.get(j)) .build()); } return new Layer(metadata.name(), features, tileExtent); } private static Property mergeFail(Property a, Property ignored) { throw new RuntimeException("Duplicate property key: " + a.getName()); } private static MltMetadata.Column decodeColumn(InputStream stream) throws IOException { final var typeCode = DecodingUtils.decodeVarint(stream); var type = MltTypeMap.Tag0x01.decodeColumnType(typeCode); String name = null; if (MltTypeMap.Tag0x01.columnTypeHasName(typeCode)) { name = DecodingUtils.decodeString(stream); } ArrayList children = null; if (MltTypeMap.Tag0x01.columnTypeHasChildren(typeCode)) { final var childCount = DecodingUtils.decodeVarint(stream); if (childCount > 0) { children = new ArrayList(childCount); for (var i = 0; i < childCount; ++i) { children.add(decodeColumn(stream).field()); } } type = new MltMetadata.FieldType( new MltMetadata.ComplexField(type.complexType().physicalType(), children), type.isNullable()); } return new MltMetadata.Column(new MltMetadata.Field(type, name)); } public static Pair parseEmbeddedMetadata(InputStream stream) throws IOException { final var table = new MltMetadata.FeatureTable(DecodingUtils.decodeString(stream)); final var extent = DecodingUtils.decodeVarint(stream); final var columnCount = DecodingUtils.decodeVarint(stream); for (int i = 0; i < columnCount; ++i) { table.columns().add(decodeColumn(stream)); } return Pair.of(table, extent); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/decoder/PropertyDecoder.java ================================================ package org.maplibre.mlt.decoder; import jakarta.annotation.Nullable; import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.BitSet; import java.util.List; import me.lemire.integercompression.IntWrapper; import org.maplibre.mlt.metadata.stream.StreamMetadataDecoder; import org.maplibre.mlt.metadata.tileset.MltMetadata; public class PropertyDecoder { private PropertyDecoder() {} /// Use present bits to reconstitute the original list with null values, if appropriate private static List unpack( List dataStream, @Nullable BitSet presentBits, int numPresentBits) { if (presentBits == null) { return dataStream; } final ArrayList outValues = new ArrayList<>(presentBits.size()); var counter = 0; for (var i = 0; i < numPresentBits; i++) { outValues.add(presentBits.get(i) ? dataStream.get(counter++) : null); } return outValues; } /// Special case for boolean columns because `BitSet` is not compatible with `List` private static List unpack( BitSet dataStream, int dataStreamSize, @Nullable BitSet presentBits, int numPresentBits) { final var numValues = (presentBits != null) ? numPresentBits : dataStreamSize; final ArrayList booleanValues = new ArrayList<>(numValues); var counter = 0; for (var i = 0; i < numValues; i++) { booleanValues.add( (presentBits == null || presentBits.get(i)) ? dataStream.get(counter++) : null); } return booleanValues; } private static Object decodeScalarPropertyColumn( byte[] data, IntWrapper offset, MltMetadata.ScalarField scalarType, boolean nullable, int numStreams) throws IOException { final BitSet presentStream; final int presentStreamSize; if (nullable) { final var presentStreamMetadata = StreamMetadataDecoder.decode(data, offset); presentStream = DecodingUtils.decodeBooleanRle( data, presentStreamMetadata.numValues(), presentStreamMetadata.byteLength(), offset); presentStreamSize = presentStreamMetadata.numValues(); numStreams -= 1; } else { presentStream = null; presentStreamSize = 0; } return switch (scalarType.physicalType()) { case BOOLEAN -> { final var dataStreamMetadata = StreamMetadataDecoder.decode(data, offset); final var dataStream = DecodingUtils.decodeBooleanRle( data, dataStreamMetadata.numValues(), dataStreamMetadata.byteLength(), offset); yield unpack(dataStream, dataStreamMetadata.numValues(), presentStream, presentStreamSize); } case UINT_32, INT_32 -> { final var dataStreamMetadata = StreamMetadataDecoder.decode(data, offset); final var signed = (scalarType.physicalType() == MltMetadata.ScalarType.INT_32); final var dataStream = IntegerDecoder.decodeIntStream(data, offset, dataStreamMetadata, signed); // otherwise, we have u32.MAX -> -1 final var values = signed ? dataStream : dataStream.stream() .map(i -> i == null ? null : Integer.toUnsignedLong(i)) .toList(); yield unpack(values, presentStream, presentStreamSize); } case UINT_64, INT_64 -> { final var dataStreamMetadata = StreamMetadataDecoder.decode(data, offset); final var signed = (scalarType.physicalType() == MltMetadata.ScalarType.INT_64); final var dataStream = IntegerDecoder.decodeLongStream(data, offset, dataStreamMetadata, signed); // otherwise, we have u64.MAX -> -1 final var values = signed ? dataStream : dataStream.stream().map(i -> i == null ? null : toUnsignedBigInteger(i)).toList(); yield unpack(values, presentStream, presentStreamSize); } case FLOAT -> { final var dataStreamMetadata = StreamMetadataDecoder.decode(data, offset); final var dataStream = FloatDecoder.decodeFloatStream(data, offset, dataStreamMetadata); yield unpack(dataStream, presentStream, presentStreamSize); } case DOUBLE -> { final var dataStreamMetadata = StreamMetadataDecoder.decode(data, offset); final var dataStream = DoubleDecoder.decodeDoubleStream(data, offset, dataStreamMetadata); yield unpack(dataStream, presentStream, presentStreamSize); } case STRING -> { final var strValues = StringDecoder.decode(data, offset, numStreams, presentStream, presentStreamSize); yield strValues.getRight(); } case UINT_8, UNRECOGNIZED, INT_8 -> throw new IllegalArgumentException( "The specified data type for the field is currently not supported: " + scalarType); }; } private static BigInteger toUnsignedBigInteger(Long value) { if (value >= 0) { return BigInteger.valueOf(value); } return BigInteger.valueOf(value).add(BigInteger.ONE.shiftLeft(64)); } public static Object decodePropertyColumn( byte[] data, IntWrapper offset, MltMetadata.Column column, int numStreams) throws IOException { if (column.isScalar()) { return decodeScalarPropertyColumn( data, offset, column.field().type().scalarType(), column.isNullable(), numStreams); } /* Handle struct which currently only supports strings as nested fields for supporting shared dictionary encoding */ if (numStreams > 1) { return StringDecoder.decodeSharedDictionary(data, offset, column).getRight(); } // var presentStreamMetadata = StreamMetadata.decode(data, offset); // var presentStream = DecodingUtils.decodeBooleanRle(data, presentStreamMetadata.numValues(), // presentStreamMetadata.byteLength(), offset); // TODO: process present stream // var values = StringDecoder.decodeSharedDictionary(data, offset, fieldMetadata); throw new IllegalArgumentException("Present stream currently not supported for Structs."); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/decoder/StringDecoder.java ================================================ package org.maplibre.mlt.decoder; import jakarta.annotation.Nullable; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.HashMap; import java.util.List; import java.util.Map; import me.lemire.integercompression.IntWrapper; import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.tuple.Triple; import org.maplibre.mlt.converter.encodings.fsst.FsstEncoder; import org.maplibre.mlt.metadata.stream.DictionaryType; import org.maplibre.mlt.metadata.stream.LengthType; import org.maplibre.mlt.metadata.stream.StreamMetadataDecoder; import org.maplibre.mlt.metadata.tileset.MltMetadata; public final class StringDecoder { private StringDecoder() {} public static Triple, HashMap, Map>> decodeSharedDictionary(byte[] data, IntWrapper offset, MltMetadata.Column column) throws IOException { List dictionaryLengthStream = null; byte[] dictionaryStream = null; List symbolLengthStream = null; byte[] symbolTableStream = null; // TODO: refactor to be spec compliant -> start by decoding the FieldMetadata, StreamMetadata // and PresentStream boolean dictionaryStreamDecoded = false; while (!dictionaryStreamDecoded) { var streamMetadata = StreamMetadataDecoder.decode(data, offset); switch (streamMetadata.physicalStreamType()) { case LENGTH: { if (LengthType.DICTIONARY.equals(streamMetadata.logicalStreamType().lengthType())) { dictionaryLengthStream = IntegerDecoder.decodeIntStream(data, offset, streamMetadata, false); } else { symbolLengthStream = IntegerDecoder.decodeIntStream(data, offset, streamMetadata, false); } break; } case DATA: { // TODO: fix -> only shared should be allowed in that case if (DictionaryType.SINGLE.equals(streamMetadata.logicalStreamType().dictionaryType()) || DictionaryType.SHARED.equals( streamMetadata.logicalStreamType().dictionaryType())) { dictionaryStream = Arrays.copyOfRange( data, offset.get(), offset.get() + streamMetadata.byteLength()); offset.set(offset.get() + streamMetadata.byteLength()); dictionaryStreamDecoded = true; } else { symbolTableStream = Arrays.copyOfRange( data, offset.get(), offset.get() + streamMetadata.byteLength()); offset.set(offset.get() + streamMetadata.byteLength()); } break; } } } List dictionary; if (symbolLengthStream != null && symbolTableStream != null && dictionaryLengthStream != null) { var decompressedLength = dictionaryLengthStream.stream().mapToInt(i -> i).sum(); var utf8Values = FsstEncoder.decode( symbolTableStream, symbolLengthStream.stream().mapToInt(i -> i).toArray(), dictionaryStream, decompressedLength); dictionary = decodeDictionary(dictionaryLengthStream, utf8Values); } else if (dictionaryLengthStream != null) { dictionary = decodeDictionary(dictionaryLengthStream, dictionaryStream); } else { throw new NotImplementedException("Expected streams missing in shared dictionary decoding"); } var presentStreams = new HashMap(); var numValues = new HashMap(); var values = new HashMap>(); for (var childField : column.field().type().complexType().children()) { var numStreams = DecodingUtils.decodeVarints(data, offset, 1)[0]; if (childField.type().scalarType() == null || childField.type().scalarType().physicalType() != MltMetadata.ScalarType.STRING) { throw new IllegalArgumentException( "Currently only scalar string fields are implemented for a struct."); } if ((numStreams > 1) != childField.type().isNullable()) { throw new IllegalArgumentException( "The number of streams for the child field " + childField.name() + " does not match its nullability."); } @Nullable BitSet presentStream = null; int presentCount = 0; if (childField.type().isNullable()) { final var presentStreamMetadata = StreamMetadataDecoder.decode(data, offset); presentCount = presentStreamMetadata.numValues(); presentStream = DecodingUtils.decodeBooleanRle( data, presentCount, presentStreamMetadata.byteLength(), offset); numStreams -= 1; } final var dataStreamMetadata = StreamMetadataDecoder.decode(data, offset); final var dataReferenceStream = IntegerDecoder.decodeIntStream(data, offset, dataStreamMetadata, false); final var valueCount = (presentStream != null) ? presentCount : dataReferenceStream.size(); final var propertyValues = new ArrayList(valueCount); var counter = 0; for (var i = 0; i < valueCount; i++) { final var present = (presentStream == null) || presentStream.get(i); propertyValues.add(present ? dictionary.get(dataReferenceStream.get(counter++)) : null); } final var columnName = column.getName() + childField.name(); numValues.put(columnName, valueCount); presentStreams.put(columnName, presentStream); values.put(columnName, propertyValues); } return Triple.of(numValues, presentStreams, values); } private static List decodeDictionary(List lengthStream, byte[] utf8Values) { // var strValues = new String(utf8Values, StandardCharsets.UTF_8); var dictionary = new ArrayList(); var dictionaryOffset = 0; for (var length : lengthStream) { // var value = strValues.substring(dictionaryOffset, dictionaryOffset + length); var value = Arrays.copyOfRange(utf8Values, dictionaryOffset, dictionaryOffset + length); dictionary.add(new String(value, StandardCharsets.UTF_8)); dictionaryOffset += length; } return dictionary; } public static Triple> decode( byte[] data, IntWrapper offset, int numStreams, @Nullable BitSet presentStream, int presentCount) throws IOException { /* * String column layouts: * -> plain -> present, length, data * -> dictionary -> present, length, dictionary, data * -> fsst dictionary -> symbolTable, symbolLength, dictionary, length, present, data * */ List dictionaryLengthStream = null; List offsetStream = null; byte[] dictionaryStream = null; List symbolLengthStream = null; byte[] symbolTableStream = null; for (var i = 0; i < numStreams; i++) { final var streamMetadata = StreamMetadataDecoder.decode(data, offset); switch (streamMetadata.physicalStreamType()) { case OFFSET: { offsetStream = IntegerDecoder.decodeIntStream(data, offset, streamMetadata, false); break; } case LENGTH: { var ls = IntegerDecoder.decodeIntStream(data, offset, streamMetadata, false); if (LengthType.DICTIONARY.equals(streamMetadata.logicalStreamType().lengthType())) { dictionaryLengthStream = ls; } else { symbolLengthStream = ls; } break; } case DATA: { var ds = Arrays.copyOfRange(data, offset.get(), offset.get() + streamMetadata.byteLength()); offset.add(streamMetadata.byteLength()); if (DictionaryType.SINGLE.equals(streamMetadata.logicalStreamType().dictionaryType())) { dictionaryStream = ds; } else { symbolTableStream = ds; } break; } } } if (symbolTableStream != null && symbolLengthStream != null && dictionaryLengthStream != null) { final var decompressedLength = dictionaryLengthStream.stream().mapToInt(i -> i).sum(); final var utf8Values = FsstEncoder.decode( symbolTableStream, symbolLengthStream.stream().mapToInt(i -> i).toArray(), dictionaryStream, decompressedLength); final var strings = decodeDictionary( presentStream, dictionaryLengthStream, utf8Values, offsetStream, presentCount); return Triple.of(strings.size(), presentStream, strings); } else if (dictionaryStream != null && dictionaryLengthStream != null) { final var strings = decodeDictionary( presentStream, dictionaryLengthStream, dictionaryStream, offsetStream, presentCount); return Triple.of(strings.size(), presentStream, strings); } else { final var strings = decodePlain(presentStream, symbolLengthStream, symbolTableStream, presentCount); return Triple.of(strings.size(), presentStream, strings); } } private static List decodePlain( @Nullable BitSet presentStream, List lengthStream, byte[] utf8Values, int presentCount) { final var numValues = (presentStream != null) ? presentCount : lengthStream.size(); final var decodedValues = new ArrayList(numValues); var lengthOffset = 0; var strOffset = 0; for (var i = 0; i < numValues; i++) { final var present = (presentStream == null) || presentStream.get(i); if (present) { final var length = lengthStream.get(lengthOffset++); final var value = new String(utf8Values, strOffset, length, StandardCharsets.UTF_8); decodedValues.add(value); strOffset += length; } else { decodedValues.add(null); } } return decodedValues; } private static List decodeDictionary( @Nullable BitSet presentStream, List lengthStream, byte[] utf8Values, List dictionaryOffsets, int presentCount) { final var dictionary = new ArrayList(); var dictionaryOffset = 0; for (var length : lengthStream) { final var value = new String( Arrays.copyOfRange(utf8Values, dictionaryOffset, dictionaryOffset + length), StandardCharsets.UTF_8); dictionary.add(value); dictionaryOffset += length; } final var numValues = (presentStream != null) ? presentCount : dictionaryOffsets.size(); final var values = new ArrayList(numValues); var offset = 0; for (var i = 0; i < numValues; i++) { final var present = (presentStream == null) || presentStream.get(i); values.add(present ? dictionary.get(dictionaryOffsets.get(offset++)) : null); } return values; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/decoder/vectorized/VectorizedDecodingUtils.java ================================================ package org.maplibre.mlt.decoder.vectorized; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import me.lemire.integercompression.Composition; import me.lemire.integercompression.FastPFOR; import me.lemire.integercompression.IntWrapper; import me.lemire.integercompression.IntegerCODEC; import me.lemire.integercompression.VariableByte; /* the redundant implementations in this class are mainly to avoid branching and therefore speed up the decoding */ public class VectorizedDecodingUtils { private VectorizedDecodingUtils() {} private static IntegerCODEC ic; public static IntBuffer decodeFastPfor( byte[] buffer, int numValues, int byteLength, IntWrapper offset) { if (ic == null) { ic = new Composition(new FastPFOR(), new VariableByte()); } /* Create a vectorized conversion from the ByteBuffer to the IntBuffer */ // TODO: get rid of that conversion IntBuffer intBuf = ByteBuffer.wrap(buffer, offset.get(), byteLength).order(ByteOrder.BIG_ENDIAN).asIntBuffer(); var bufferSize = (int) Math.ceil(byteLength / 4d); int[] intValues = new int[bufferSize]; for (var i = 0; i < intValues.length; i++) { intValues[i] = intBuf.get(i); } int[] decodedValues = new int[numValues]; ic.uncompress(intValues, new IntWrapper(0), intValues.length, decodedValues, new IntWrapper(0)); offset.add(byteLength); return IntBuffer.wrap(decodedValues); } /* Delta encoding ------------------------------------------------------------------------------*/ /* * In place decoding of the zigzag delta encoded Vec2. * Inspired by https://github.com/lemire/JavaFastPFOR/blob/master/src/main/java/me/lemire/integercompression/differential/Delta.java */ public static void decodeComponentwiseDeltaVec2(int[] data) { data[0] = (data[0] >>> 1) ^ ((data[0] << 31) >> 31); data[1] = (data[1] >>> 1) ^ ((data[1] << 31) >> 31); int sz0 = data.length / 4 * 4; int i = 2; if (sz0 >= 4) { for (; i < sz0 - 4; i += 4) { var x1 = data[i]; var y1 = data[i + 1]; var x2 = data[i + 2]; var y2 = data[i + 3]; data[i] = ((x1 >>> 1) ^ ((x1 << 31) >> 31)) + data[i - 2]; data[i + 1] = ((y1 >>> 1) ^ ((y1 << 31) >> 31)) + data[i - 1]; data[i + 2] = ((x2 >>> 1) ^ ((x2 << 31) >> 31)) + data[i]; data[i + 3] = ((y2 >>> 1) ^ ((y2 << 31) >> 31)) + data[i + 1]; } } for (; i != data.length; i += 2) { data[i] = ((data[i] >>> 1) ^ ((data[i] << 31) >> 31)) + data[i - 2]; data[i + 1] = ((data[i + 1] >>> 1) ^ ((data[i + 1] << 31) >> 31)) + data[i - 1]; } } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/json/Json.java ================================================ package org.maplibre.mlt.json; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.Strictness; import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; import java.util.LinkedHashMap; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.io.geojson.GeoJsonWriter; import org.maplibre.mlt.data.Feature; import org.maplibre.mlt.data.Layer; import org.maplibre.mlt.data.MapLibreTile; import org.maplibre.mlt.data.MapboxVectorTile; import org.maplibre.mlt.data.Property; /** Utility for converting MVT and MLT tiles to JSON and GeoJSON. */ public final class Json { // GeoJSON does not support non-numeric floats; use Rust-style string tokens for // cross-implementation consistency. private static final String F32_NAN = "f32::NAN"; private static final String F32_INFINITY = "f32::INFINITY"; private static final String F32_NEG_INFINITY = "f32::NEG_INFINITY"; private static final String F64_NAN = "f64::NAN"; private static final String F64_INFINITY = "f64::INFINITY"; private static final String F64_NEG_INFINITY = "f64::NEG_INFINITY"; private static final Type linkedHashMapType = new TypeToken>() {}.getType(); private Json() {} /** * Convert a MapboxVectorTile to a JSON string with the same structure as the MVT spec. The * geometry is represented as a WKT string for comparison with MLT output. Note that this is not * GeoJSON and is intended for testing/debugging purposes only. * * @param tile the tile to convert * @param pretty if true, the output will be pretty-printed, otherwise, it will be compact * @return a JSON string representing the tile */ public static String toJson(MapboxVectorTile tile, boolean pretty) { return createGson(pretty).toJson(toJsonObjects(tile)); } /** * Convert a MapLibreTile to a JSON string with the same structure as the MLT spec. The geometry * is represented as a WKT string for comparison with MVT output. Note that this is not GeoJSON * and is intended for testing/debugging purposes only. * * @param tile the tile to convert * @param pretty if true, the output will be pretty-printed, otherwise, it will be compact * @return a JSON string representing the tile */ public static String toJson(MapLibreTile tile, boolean pretty) { return createGson(pretty).toJson(toJsonObjects(tile)); } /** * Convert a MapLibreTile to a GeoJSON string. The layer name and tile extent are included as * properties with keys "_layer" and "_extent", respectively, for testing/debugging purposes. * * @param tile the tile to convert * @param pretty if true, the output will be pretty-printed, otherwise, it will be compact * @return a GeoJSON string representing the tile */ public static String toGeoJson(MapLibreTile tile, boolean pretty) { final var gson = createGson(pretty); return gson.toJson(toGeoJsonObjects(tile, gson)); } private static Gson createGson(boolean pretty) { final var builder = new GsonBuilder() .disableJdkUnsafe() .disableHtmlEscaping() // not .serializeNulls() .serializeSpecialFloatingPointValues() .setStrictness(Strictness.STRICT); return (pretty ? builder.setPrettyPrinting() : builder).create(); } private static Object floatToken(Float value) { if (value.isNaN()) { return F32_NAN; } else if (value == Float.POSITIVE_INFINITY) { return F32_INFINITY; } else if (value == Float.NEGATIVE_INFINITY) { return F32_NEG_INFINITY; } return value; } private static Object doubleToken(Double value) { if (value.isNaN()) { return F64_NAN; } else if (value == Double.POSITIVE_INFINITY) { return F64_INFINITY; } else if (value == Double.NEGATIVE_INFINITY) { return F64_NEG_INFINITY; } return value; } /** Recursively replace Float/Double NaN and +/-Infinity with GeoJSON string tokens. */ private static Object floatsAsStrings(Object obj) { return switch (obj) { case Float value -> floatToken(value); case Double value -> doubleToken(value); case Iterable iterable -> StreamSupport.stream(iterable.spliterator(), false).map(Json::floatsAsStrings).toList(); case Map map -> map.entrySet().stream() .collect( Collectors.toMap( Map.Entry::getKey, entry -> floatsAsStrings(entry.getValue()), Json::failOnDuplicate, LinkedHashMap::new)); default -> obj; }; } private static Map toJsonObjects(MapLibreTile mlTile) { return Map.of("layers", mlTile.getLayerStream().map(Json::toJson).toList()); } private static Map toJson(Layer layer) { final var map = new LinkedHashMap(); map.put("name", layer.name()); map.put("extent", layer.tileExtent()); map.put("features", layer.features().stream().map(Json::toJson).toList()); return map; } private static Map toJson(Feature feature) { final var featureMap = new LinkedHashMap(); if (feature.hasId()) { featureMap.put("id", feature.getId()); } featureMap.put("geometry", feature.getGeometry().toString()); // Keep non-null properties only to facilitate direct comparison with MVT output. final var propertyMap = feature .getPropertyStream() .filter(entry -> entry.getValue(feature.getIndex()) != null) .collect( Collectors.toMap( Property::getName, p -> p.getValue(feature.getIndex()), Json::failOnDuplicate, LinkedHashMap::new)); featureMap.put("properties", propertyMap); return featureMap; } public static Map toGeoJsonObjects(MapLibreTile mlTile, Gson gson) { final var featureCollectionMap = new LinkedHashMap(); featureCollectionMap.put("type", "FeatureCollection"); featureCollectionMap.put( "features", mlTile .getLayerStream() .flatMap( layer -> layer.features().stream() .map(feature -> featureToGeoJson(layer, feature, gson))) .toList()); return featureCollectionMap; } private static Map featureToGeoJson(Layer layer, Feature feature, Gson gson) { final var featureMap = new LinkedHashMap(); featureMap.put("type", "Feature"); if (feature.hasId()) { featureMap.put("id", feature.getId()); } final var props = getSortedNonNullProperties(feature); props.put("_layer", layer.name()); props.put("_extent", layer.tileExtent()); featureMap.put("properties", floatsAsStrings(props)); final var geom = feature.getGeometry(); featureMap.put("geometry", geom == null ? null : geometryToGeoJson(geom, gson)); return featureMap; } private static Object failOnDuplicate(Object a, Object b) { throw new IllegalStateException("Duplicate key"); } private static SortedMap getSortedNonNullProperties(Feature feature) { return feature .getPropertyStream() .filter(entry -> entry.getValue(feature.getIndex()) != null) .collect( Collectors.toMap( Property::getName, p -> p.getValue(feature.getIndex()), Json::failOnDuplicate, TreeMap::new)); } private static Map geometryToGeoJson(Geometry geometry, Gson gson) { var writer = new GeoJsonWriter(); writer.setEncodeCRS(false); final LinkedHashMap map = gson.fromJson(writer.write(geometry), linkedHashMapType); if (map.containsKey("coordinates")) { map.put("coordinates", intifyCoordinates(map.get("coordinates"))); } return map; } /** Recursively convert whole-number doubles to longs inside a coordinates structure. */ private static Object intifyCoordinates(Object obj) { if (obj instanceof Iterable list) { return StreamSupport.stream(list.spliterator(), false).map(Json::intifyCoordinates).toList(); } if (obj instanceof Double value && value == Math.floor(value) && !value.isInfinite() && !value.isNaN()) { return value.longValue(); } return obj; } private static Map toJsonObjects(MapboxVectorTile mvTile) { return Map.of("layers", mvTile.getLayerStream().map(Json::toJson).toList()); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/metadata/stream/DictionaryType.java ================================================ package org.maplibre.mlt.metadata.stream; public enum DictionaryType { NONE, SINGLE, SHARED, VERTEX, MORTON, FSST } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/metadata/stream/LengthType.java ================================================ package org.maplibre.mlt.metadata.stream; public enum LengthType { VAR_BINARY, GEOMETRIES, PARTS, RINGS, TRIANGLES, SYMBOL, DICTIONARY } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/metadata/stream/LogicalLevelTechnique.java ================================================ package org.maplibre.mlt.metadata.stream; public enum LogicalLevelTechnique { NONE, DELTA, COMPONENTWISE_DELTA, RLE, MORTON, /* Pseudodecimal Encoding of floats -> only for the exponent integer part an additional logical level technique is used. * Both exponent and significant parts are encoded with the same physical level technique */ PDE; } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/metadata/stream/LogicalStreamType.java ================================================ package org.maplibre.mlt.metadata.stream; public class LogicalStreamType { private DictionaryType dictionaryType; private OffsetType offsetType; private LengthType lengthType; public LogicalStreamType(DictionaryType dictionaryType) { this.dictionaryType = dictionaryType; } public LogicalStreamType(OffsetType offsetType) { this.offsetType = offsetType; } public LogicalStreamType(LengthType lengthType) { this.lengthType = lengthType; } public DictionaryType dictionaryType() { return this.dictionaryType; } public OffsetType offsetType() { return this.offsetType; } public LengthType lengthType() { return this.lengthType; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/metadata/stream/MortonEncodedStreamMetadata.java ================================================ package org.maplibre.mlt.metadata.stream; import java.io.IOException; import java.util.ArrayList; import me.lemire.integercompression.IntWrapper; import org.maplibre.mlt.converter.encodings.EncodingUtils; import org.maplibre.mlt.decoder.DecodingUtils; public class MortonEncodedStreamMetadata extends StreamMetadata { private final int numBits; private final int coordinateShift; // TODO: refactor -> use builder pattern public MortonEncodedStreamMetadata( PhysicalStreamType physicalStreamType, LogicalStreamType logicalStreamType, LogicalLevelTechnique logicalLevelTechnique1, LogicalLevelTechnique logicalLevelTechnique2, PhysicalLevelTechnique physicalLevelTechnique, int numValues, int byteLength, int numBits, int coordinateShift) { super( physicalStreamType, logicalStreamType, logicalLevelTechnique1, logicalLevelTechnique2, physicalLevelTechnique, numValues, byteLength); this.numBits = numBits; this.coordinateShift = coordinateShift; } public ArrayList encode() throws IOException { final var result = super.encode(); result.add(EncodingUtils.encodeVarints(new int[] {numBits, coordinateShift}, false, false)); return result; } public static MortonEncodedStreamMetadata decodePartial( StreamMetadata streamMetadata, byte[] tile, IntWrapper offset) throws IOException { var mortonInfo = DecodingUtils.decodeVarints(tile, offset, 2); return new MortonEncodedStreamMetadata( streamMetadata.physicalStreamType(), streamMetadata.logicalStreamType(), streamMetadata.logicalLevelTechnique1(), streamMetadata.logicalLevelTechnique2(), streamMetadata.physicalLevelTechnique(), streamMetadata.numValues(), streamMetadata.byteLength(), mortonInfo[0], mortonInfo[1]); } public int numBits() { return this.numBits; } public int coordinateShift() { return this.coordinateShift; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/metadata/stream/OffsetType.java ================================================ package org.maplibre.mlt.metadata.stream; public enum OffsetType { VERTEX, INDEX, STRING, KEY } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/metadata/stream/PhysicalLevelTechnique.java ================================================ package org.maplibre.mlt.metadata.stream; public enum PhysicalLevelTechnique { NONE, /* Preferred option, tends to produce the best compression ratio and decoding performance. * But currently only limited to 32 bit integer. */ FAST_PFOR, /* Can produce better results in combination with a heavyweight compression scheme like Gzip. * Simple compression scheme where the decoder are easier to implement compared to FastPfor.*/ VARINT } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/metadata/stream/PhysicalStreamType.java ================================================ package org.maplibre.mlt.metadata.stream; public enum PhysicalStreamType { PRESENT, DATA, OFFSET, LENGTH } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/metadata/stream/RleEncodedStreamMetadata.java ================================================ package org.maplibre.mlt.metadata.stream; import java.io.IOException; import java.util.ArrayList; import me.lemire.integercompression.IntWrapper; import org.maplibre.mlt.converter.encodings.EncodingUtils; import org.maplibre.mlt.decoder.DecodingUtils; public class RleEncodedStreamMetadata extends StreamMetadata { int runs; int numRleValues; // TODO: refactor -> use builder pattern /** * Only used for RLE encoded integer values. Not needed for rle encoded boolean and byte values. * * @param numValues After LogicalLevelTechnique was applied -> numRuns + numValues * @param runs Length of the runs array * @param numRleValues Used for pre-allocating the arrays on the client for faster decoding */ public RleEncodedStreamMetadata( PhysicalStreamType physicalStreamType, LogicalStreamType logicalStreamType, LogicalLevelTechnique logicalLevelTechnique1, LogicalLevelTechnique logicalLevelTechnique2, PhysicalLevelTechnique physicalLevelTechnique, int numValues, int byteLength, int runs, int numRleValues) { super( physicalStreamType, logicalStreamType, logicalLevelTechnique1, logicalLevelTechnique2, physicalLevelTechnique, numValues, byteLength); this.runs = runs; this.numRleValues = numRleValues; } public ArrayList encode() throws IOException { final var result = super.encode(); result.add(EncodingUtils.encodeVarints(new int[] {runs, numRleValues}, false, false)); return result; } public static RleEncodedStreamMetadata decodePartial( StreamMetadata streamMetadata, byte[] tile, IntWrapper offset) throws IOException { var rleInfo = DecodingUtils.decodeVarints(tile, offset, 2); return new RleEncodedStreamMetadata( streamMetadata.physicalStreamType(), streamMetadata.logicalStreamType(), streamMetadata.logicalLevelTechnique1(), streamMetadata.logicalLevelTechnique2(), streamMetadata.physicalLevelTechnique(), streamMetadata.numValues(), streamMetadata.byteLength(), rleInfo[0], rleInfo[1]); } public int runs() { return this.runs; } public int numRleValues() { return this.numRleValues; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/metadata/stream/StreamMetadata.java ================================================ package org.maplibre.mlt.metadata.stream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import me.lemire.integercompression.IntWrapper; import org.maplibre.mlt.converter.encodings.EncodingUtils; import org.maplibre.mlt.decoder.DecodingUtils; public class StreamMetadata { private final PhysicalStreamType physicalStreamType; private final LogicalStreamType logicalStreamType; private final LogicalLevelTechnique logicalLevelTechnique1; private final LogicalLevelTechnique logicalLevelTechnique2; private final PhysicalLevelTechnique physicalLevelTechnique; /* After logical Level technique was applied -> when rle is used it is the length of the runs and values array */ private final int numValues; private final int byteLength; // TODO: refactor -> use builder pattern public StreamMetadata( PhysicalStreamType physicalStreamType, LogicalStreamType logicalStreamType, LogicalLevelTechnique logicalLevelTechnique1, LogicalLevelTechnique logicalLevelTechnique2, PhysicalLevelTechnique physicalLevelTechnique, int numValues, int byteLength) { this.physicalStreamType = physicalStreamType; this.logicalStreamType = logicalStreamType; this.logicalLevelTechnique1 = logicalLevelTechnique1; this.logicalLevelTechnique2 = logicalLevelTechnique2; this.physicalLevelTechnique = physicalLevelTechnique; this.numValues = numValues; this.byteLength = byteLength; } private int getLogicalType() { if (logicalStreamType == null) { return 0; } if (logicalStreamType.dictionaryType() != null) { return logicalStreamType.dictionaryType().ordinal(); } if (logicalStreamType.lengthType() != null) { return logicalStreamType.lengthType().ordinal(); } return logicalStreamType.offsetType().ordinal(); } public ArrayList encode() throws IOException { return encode(5); } public ArrayList encode(int estimatedAdditionalStreams) throws IOException { final var encodedStreamType = (byte) ((physicalStreamType.ordinal()) << 4 | getLogicalType()); final var encodedEncodingScheme = (byte) (logicalLevelTechnique1.ordinal() << 5 | logicalLevelTechnique2.ordinal() << 2 | physicalLevelTechnique.ordinal()); final var size = 2 + EncodingUtils.getVarIntSize(numValues) + EncodingUtils.getVarIntSize(byteLength); final var varintBuffer = ByteBuffer.wrap(new byte[size]); varintBuffer.put(encodedStreamType); varintBuffer.put(encodedEncodingScheme); EncodingUtils.putVarInt(numValues, varintBuffer); EncodingUtils.putVarInt(byteLength, varintBuffer); final var result = new ArrayList(1 + estimatedAdditionalStreams); result.add(varintBuffer.array()); return result; } public static StreamMetadata decode(byte[] tile, IntWrapper offset) throws IOException { var streamType = tile[offset.get()]; var physicalStreamType = PhysicalStreamType.values()[streamType >> 4]; LogicalStreamType logicalStreamType = switch (physicalStreamType) { case DATA -> new LogicalStreamType(DictionaryType.values()[streamType & 0xf]); case OFFSET -> new LogicalStreamType(OffsetType.values()[streamType & 0xf]); case LENGTH -> new LogicalStreamType(LengthType.values()[streamType & 0xf]); default -> null; }; offset.increment(); var encodingsHeader = tile[offset.get()] & 0xFF; var logicalLevelTechnique1 = LogicalLevelTechnique.values()[encodingsHeader >> 5]; var logicalLevelTechnique2 = LogicalLevelTechnique.values()[encodingsHeader >> 2 & 0x7]; var physicalLevelTechnique = PhysicalLevelTechnique.values()[encodingsHeader & 0x3]; offset.increment(); var sizeInfo = DecodingUtils.decodeVarints(tile, offset, 2); var numValues = sizeInfo[0]; var byteLength = sizeInfo[1]; return new StreamMetadata( physicalStreamType, logicalStreamType, logicalLevelTechnique1, logicalLevelTechnique2, physicalLevelTechnique, numValues, byteLength); } public PhysicalStreamType physicalStreamType() { return this.physicalStreamType; } public LogicalStreamType logicalStreamType() { return this.logicalStreamType; } public LogicalLevelTechnique logicalLevelTechnique1() { return this.logicalLevelTechnique1; } public LogicalLevelTechnique logicalLevelTechnique2() { return this.logicalLevelTechnique2; } public PhysicalLevelTechnique physicalLevelTechnique() { return this.physicalLevelTechnique; } public int numValues() { return this.numValues; } public int byteLength() { return this.byteLength; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/metadata/stream/StreamMetadataDecoder.java ================================================ package org.maplibre.mlt.metadata.stream; import java.io.IOException; import me.lemire.integercompression.IntWrapper; public class StreamMetadataDecoder { public static StreamMetadata decode(byte[] tile, IntWrapper offset) throws IOException { var streamMetadata = StreamMetadata.decode(tile, offset); /* Currently morton can't be combined with RLE only with delta */ if (streamMetadata.logicalLevelTechnique1().equals(LogicalLevelTechnique.MORTON)) { return MortonEncodedStreamMetadata.decodePartial(streamMetadata, tile, offset); } /* Boolean rle doesn't need additional information */ else if ((LogicalLevelTechnique.RLE.equals(streamMetadata.logicalLevelTechnique1()) || LogicalLevelTechnique.RLE.equals(streamMetadata.logicalLevelTechnique2())) && !PhysicalLevelTechnique.NONE.equals(streamMetadata.physicalLevelTechnique())) { return RleEncodedStreamMetadata.decodePartial(streamMetadata, tile, offset); } return streamMetadata; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/metadata/tileset/MltMetadata.java ================================================ package org.maplibre.mlt.metadata.tileset; import jakarta.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.SequencedCollection; import org.jetbrains.annotations.NotNull; public final class MltMetadata { private MltMetadata() {} public static FieldType scalarFieldType(@NotNull ScalarType type, boolean isNullable) { return new FieldType(new ScalarField(type), isNullable); } public static FieldType idFieldType(boolean hasLongId, boolean isNullable) { return new FieldType(new ScalarField(LogicalScalarType.ID, hasLongId), isNullable); } public static FieldType structFieldType(@Nullable SequencedCollection children) { return new FieldType(new ComplexField(ComplexType.STRUCT, children), false); } public static FieldType geometryFieldType() { return new FieldType(new ComplexField(ComplexType.GEOMETRY), false); } public static FieldType complexFieldType(@NotNull ComplexType type, boolean isNullable) { return new FieldType(new ComplexField(type, null), isNullable); } /// Describes how the column's values are associated with features and geometries public enum ColumnScope { /** 1:1 Mapping of property and feature to id and geometry */ FEATURE, /** For M-Values, 1:1 Mapping for property and vertex */ VERTEX, UNRECOGNIZED } public enum ScalarType { BOOLEAN, INT_8, UINT_8, INT_32, UINT_32, INT_64, UINT_64, FLOAT, DOUBLE, STRING, UNRECOGNIZED } public enum ComplexType { GEOMETRY, STRUCT, MAP, // nested property map UNRECOGNIZED } public enum LogicalScalarType { ID, UNRECOGNIZED } public enum LogicalComplexType { /** physical type: list<UInt8> */ BINARY, /** * physical type: map<vec2<double, T>> -> special data structure which can be * used for an efficient representation of linear referencing */ RANGE_MAP, UNRECOGNIZED } public static final class TileSetMetadata { public List featureTables = new ArrayList<>(); public String name; public String description; public Object attribution; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public Optional minZoom = Optional.empty(); @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public Optional maxZoom = Optional.empty(); public List bounds = new ArrayList<>(); public List center = new ArrayList<>(); } public static final record FeatureTable( @NotNull String name, @NotNull SequencedCollection columns) { public FeatureTable { Objects.requireNonNull(name); columns = (columns != null) ? columns : new ArrayList<>(); } public FeatureTable(String name) { this(name, null); } public FeatureTable(String name, int initialColumnCapacity) { this(name, new ArrayList<>(initialColumnCapacity)); } } /// The type of data in a Field /// @param scalarType A scalar type, if set. Mutually exclusive with complexType. /// @param complexType A complex type, if set. Mutually exclusive with scalarType. /// @param isNullable Whether the field can be null public static final record FieldType( @Nullable ScalarField scalarType, @Nullable ComplexField complexType, boolean isNullable) { public FieldType { if ((scalarType == null) == (complexType == null)) { throw new IllegalStateException("FieldType must be either scalar or complex"); } } /// Create a scalar type /// @param scalarType the type /// @param isNullable whether the field can be null public FieldType(@Nullable ScalarField scalarType, boolean isNullable) { this(scalarType, null, isNullable); } /// Create a complex type /// @param complexType the type /// @param isNullable whether the field can be null public FieldType(@Nullable ComplexField complexType, boolean isNullable) { this(null, complexType, isNullable); } /// Check if this field is of the given scalar type /// @param type the scalar type to check against /// @return true if this field is of the given scalar type public boolean is(ScalarType type) { return (scalarType != null && scalarType.physicalType == type); } /// Check if this field is of the given logical scalar type /// @param type the logical scalar type to check against /// @return true if this field is of the given logical scalar type public boolean is(LogicalScalarType type) { return (scalarType != null && scalarType.logicalType == type); } /// Check if this field is of the given complex type /// @param type the complex type to check against /// @return true if this field is of the given complex type public boolean is(ComplexType type) { return (complexType != null && complexType.physicalType == type); } /// Check if this field is of the given logical complex type /// @param type the logical complex type to check against /// @return true if this field is of the given logical complex type public boolean is(LogicalComplexType type) { return (complexType != null && complexType.logicalType == type); } /// Get the physical scalar type of this field, if applicable /// @return an Optional containing the physical scalar type of this field, or an empty Optional /// if this field is not a scalar type public Optional getScalarType() { return (scalarType != null) ? Optional.ofNullable(scalarType.physicalType) : Optional.empty(); } /// Get the logical scalar type of this field, if applicable /// @return an Optional containing the logical scalar type of this field, or an empty Optional /// if this field is not a scalar type public Optional getLogicalScalarType() { return (scalarType != null) ? Optional.ofNullable(scalarType.logicalType) : Optional.empty(); } /// Get the physical complex type of this field, if applicable /// @return an Optional containing the physical complex type of this field, or an empty Optional /// if this field is not a complex type public Optional getComplexType() { return (complexType != null) ? Optional.ofNullable(complexType.physicalType) : Optional.empty(); } /// Get the logical complex type of this field, if applicable /// @return an Optional containing the logical complex type of this field, or an empty Optional /// if this field is not a complex type public Optional getLogicalComplexType() { return (complexType != null) ? Optional.ofNullable(complexType.logicalType) : Optional.empty(); } /// Get the child fields of this field, if applicable (e.g., for STRUCT or MAP types) /// @return an Optional containing a SequencedCollection of child fields, or an empty Optional public Optional> getChildren() { return (complexType != null) ? Optional.ofNullable(complexType.children) : Optional.empty(); } } /// A field may be a column or nested as a child of a complex type /// @param name The name of the field. May be null for nested fields or implicit types (e.g, ID). /// @param type The type of the field. Must be non-null. public static final record Field(@NotNull FieldType type, @Nullable String name) { public Field { Objects.requireNonNull(type); } public Field(@NotNull FieldType type) { this(type, null); } } /// Column are top-level types in the schema /// @param field The field associated with this column. Must be non-null. /// @param columnScope The column scope, which must be either FEATURE or VERTEX public static final record Column(@NotNull Field field, @NotNull ColumnScope columnScope) { /// Create a column with the given field and column scope /// @param field the field associated with this column /// @param columnScope the column scope, which must be either FEATURE or VERTEX public Column { if (columnScope != ColumnScope.FEATURE && columnScope != ColumnScope.VERTEX) { throw new IllegalStateException("Column scope must be either FEATURE or VERTEX"); } } /// Create a column with the given field and default scope of FEATURE /// @param field the field associated with this column public Column(Field field) { this(field, ColumnScope.FEATURE); } /// Create a column with the given type and default scope of FEATURE /// @param type the type of the field associated with this column public Column(FieldType type) { this(new Field(type), ColumnScope.FEATURE); } /// Get the name of this column /// @return the name of this column, or null if unnamed public String getName() { return field.name; } /// Check if this column is nullable /// @return true if this column is nullable, false otherwise public boolean isNullable() { return field.type.isNullable; } /// Check if this column is a scalar type /// @return true if this column is a scalar type, false otherwise public boolean isScalar() { return field.type.scalarType != null; } /// Check if this column is a complex type /// @return true if this column is a complex type, false otherwise public boolean isComplex() { return field.type.complexType != null; } /// Check if this column is of the given scalar type /// @param type the scalar type to check against /// @return true if this column is of the given scalar type public boolean is(ScalarType type) { return field.type.is(type); } /// Check if this column is of the given logical scalar type /// @param type the logical scalar type to check against /// @return true if this column is of the given logical scalar type public boolean is(LogicalScalarType type) { return field.type.is(type); } /// Check if this column is of the given complex type /// @param type the complex type to check against /// @return true if this column is of the given complex type public boolean is(ComplexType type) { return field.type.is(type); } /// Check if this column is of the given logical complex type /// @param type the logical complex type to check against /// @return true if this column is of the given logical complex type public boolean is(LogicalComplexType type) { return field.type.is(type); } /// Get the physical scalar type of this column, if applicable /// @return an Optional containing the physical scalar type of this column, or an empty Optional // if this column is not a scalar type public Optional getScalarType() { return field.type.getScalarType(); } /// Get the logical scalar type of this column, if applicable /// @return an Optional containing the logical scalar type of this column, or an empty Optional // if this column is not a scalar type public Optional getLogicalScalarType() { return field.type.getLogicalScalarType(); } /// Get the physical complex type of this column, if applicable /// @return an Optional containing the physical complex type of this column, or an empty // Optional if this column is not a complex type public Optional getComplexType() { return field.type.getComplexType(); } /// Get the logical complex type of this column, if applicable /// @return an Optional containing the logical complex type of this column, or an empty Optional // if this column is not a complex type public Optional getLogicalComplexType() { return field.type.getLogicalComplexType(); } /// Get the child fields of this column, if applicable (e.g., for STRUCT or MAP types) /// @return an Optional containing a SequencedCollection of child fields, or an empty Optional public Optional> getChildren() { return field.type.getChildren(); } } /// A scalar field, which may be a physical type or a simple logical type (e.g., ID) /// @param physicalType The physical type, if applicable. Mutually exclusive with logicalType /// @param logicalType The logical type, if applicable. Mutually exclusive with physicalType /// @param hasLongId Whether the ID field has long ids (i.e., 64-bit integers) public static final record ScalarField( @Nullable ScalarType physicalType, @Nullable LogicalScalarType logicalType, boolean hasLongId) { public ScalarField { if ((physicalType == null) == (logicalType == null)) { throw new IllegalStateException( "ScalarField must have either a physical type or a logical type"); } } /// Create a scalar field with the given physical type /// @param type the physical type of the field public ScalarField(@NotNull ScalarType type) { this(type, null, false); } /// Create a scalar field with the given logical type /// @param type the logical type of the field /// @param hasLongId whether the ID field has long ids (i.e., 64-bit integers) public ScalarField(@NotNull LogicalScalarType type, boolean hasLongId) { this(null, type, hasLongId); } } /// A complex field /// @param physicalType The physical type, if applicable. Mutually exclusive with logicalType /// @param logicalType The logical type, if applicable. Mutually exclusive with physicalType. /// @param children The child fields of this complex field, if applicable public static final record ComplexField( @Nullable ComplexType physicalType, @Nullable LogicalComplexType logicalType, @NotNull SequencedCollection children) { public ComplexField { if ((physicalType == null) == (logicalType == null)) { throw new IllegalStateException( "ComplexField must have either a physical type or a logical type"); } children = (children != null) ? children : new ArrayList<>(); } /// Create a complex field with the given physical type and no children /// @param type the physical type of the field public ComplexField(@NotNull ComplexType type) { this(type, null); } /// Create a complex field with the given physical type and children /// @param type the physical type of the field /// @param children the child fields of this complex field, or null if no children public ComplexField(@NotNull ComplexType type, @Nullable SequencedCollection children) { this(type, null, children); } } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/util/ByteArrayUtil.java ================================================ package org.maplibre.mlt.util; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Collection; public final class ByteArrayUtil { private ByteArrayUtil() {} public static int totalLength(Collection buffers) { return buffers.stream().mapToInt(x -> x.length).sum(); } public static T concat(T stream, Collection buffers) throws IOException { for (var buffer : buffers) { stream.write(buffer); } return stream; } public static byte[] concat(Collection buffers) throws IOException { try (final var stream = new ByteArrayOutputStream(totalLength(buffers))) { return concat(stream, buffers).toByteArray(); } } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/util/ExceptionUtil.java ================================================ package org.maplibre.mlt.util; import java.util.function.Function; import org.apache.commons.lang3.exception.UncheckedException; public class ExceptionUtil { @FunctionalInterface public interface ThrowingFunction { R apply(T t) throws E; } /// Wraps a function that throws a checked exception in a RuntimeException, allowing /// it to be used in contexts that don't allow checked exceptions, e.g., streams. public static Function unchecked(ThrowingFunction f) { return t -> { try { return f.apply(t); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new UncheckedException(e); } }; } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/util/OptionalUtil.java ================================================ package org.maplibre.mlt.util; import java.util.Comparator; import java.util.Optional; import java.util.function.BiFunction; import org.jetbrains.annotations.NotNull; public class OptionalUtil { /// Compare two Optional values with the specified comparator. // Empty is not less than any value, and any value is less than empty. @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public static > boolean isLessThan( @NotNull Optional a, @NotNull Optional b) { return isLessThan(a, b, Comparator.naturalOrder()); } /// Compare two Optional values. // Empty is not less than any value, and any value is less than empty. @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private static boolean isLessThan( @NotNull Optional a, @NotNull Optional b, @NotNull Comparator comparator) { if (a.isEmpty()) { return false; } if (b.isEmpty()) { return true; } return comparator.compare(a.get(), b.get()) < 0; } /// Map two Optional values to a single Optional value using the specified function. /// The result is empty if either input is empty. @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public static Optional map( Optional a, Optional b, BiFunction f) { return a.flatMap(av -> b.map(bv -> f.apply(av, bv))); } } ================================================ FILE: java/mlt-core/src/main/java/org/maplibre/mlt/util/StreamUtil.java ================================================ package org.maplibre.mlt.util; import java.util.Iterator; import java.util.Objects; import java.util.Optional; import java.util.Spliterator; import java.util.Spliterators; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; import org.apache.commons.lang3.tuple.Pair; public final class StreamUtil { private StreamUtil() {} /// Combine two streams into one by applying a function to pairs of elements from the input /// streams. /// The resulting stream will have the same number of elements as the shorter input stream, and /// any remaining elements in the longer stream will be ignored. /// The resulting stream: /// - Is lazy and will only compute elements as needed, allowing for efficient processing of large /// or infinite streams. /// - Is parallel if either of the input streams is parallel. /// - Has the intersection of the characteristics of input streams with DISTINCT and SORTED /// removed /// - Is sized if both input streams are sized, the size being the minimum of the sizes of the /// input streams /// @param a the first input stream, providing the first argument to the function /// @param b the second input stream, providing the second argument to the function /// @param f the function to apply to pairs of elements from the input streams /// @return a stream of the results of applying the function to pairs of elements from the input /// streams /// @throws NullPointerException if any of the input streams or the function is null // Really, this isn't in `Stream` anywhere? public static Stream zip( Stream a, Stream b, BiFunction f) { Objects.requireNonNull(f); final var spliterA = Objects.requireNonNull(a).spliterator(); final var spliterB = Objects.requireNonNull(b).spliterator(); final var cIterator = new ZipIterator(spliterA, spliterB, f); // Eliminate DISTINCT and SORTED characteristics final int characteristics = spliterA.characteristics() & spliterB.characteristics() & ~(Spliterator.DISTINCT | Spliterator.SORTED); // the zipped result will be the size of the smaller stream final var sizeIfKnown = ((characteristics & Spliterator.SIZED) != 0) ? Math.min(spliterA.getExactSizeIfKnown(), spliterB.getExactSizeIfKnown()) : -1; return StreamSupport.stream( (sizeIfKnown < 0) ? Spliterators.spliteratorUnknownSize(cIterator, characteristics) : Spliterators.spliterator(cIterator, sizeIfKnown, characteristics), a.isParallel() || b.isParallel()); } private static final class ZipIterator implements Iterator { private final Iterator aIterator; private final Iterator bIterator; private final BiFunction function; ZipIterator( Spliterator a, Spliterator b, BiFunction f) { aIterator = Spliterators.iterator(a); bIterator = Spliterators.iterator(b); function = f; } @Override public boolean hasNext() { // Stop when either stream is exhausted, ignoring remaining elements in the longer stream return aIterator.hasNext() && bIterator.hasNext(); } @Override public C next() { return function.apply(aIterator.next(), bIterator.next()); } } /// Return a lazy stream of `Pair<>` objects public static Stream> zip(Stream a, Stream b) { return zip(a, b, Pair::of); } /// Run the given function for each pair of elements from the two streams. /// @return the number of pairs processed public static long zipEach( Stream a, Stream b, BiConsumer f) { return zip( a, b, (x, y) -> { f.accept(x, y); return 1L; }) .reduce(0L, Long::sum); } /// Filter a stream of objects by those that are of the given type or a subtype. /// Intended for use with `Stream.flatMap` to safely change the type within a sequence of chained /// operations. /// @param targetType the type to filter by /// @return A stream of the target type public static Function> ofType( Class targetType) { return value -> targetType.isInstance(value) ? Stream.of(targetType.cast(value)) : Stream.empty(); } /// The equivalent of #ofType for stream-like operations on optional values. /// Intended for use with `Optional.flatMap` to safelyt change the type within a sequence of /// chained operations. /// @param targetType the type /// @return An optional of the target type if the value is of that type, otherwise empty public static Function> optionalOfType( Class targetType) { return value -> targetType.isInstance(value) ? Optional.of(targetType.cast(value)) : Optional.empty(); } } ================================================ FILE: java/mlt-core/src/main/java/springmeyer/Pbf.java ================================================ package org.springmeyer; import java.nio.charset.StandardCharsets; import java.util.Arrays; /* * This is a partial port of https://github.com/mapbox/pbf to Java. */ public class Pbf { private byte[] buf; public int pos; public int type; public int length; public static final int Varint = 0; public static final int Fixed64 = 1; public static final int Bytes = 2; public static final int Fixed32 = 5; private static final long SHIFT_LEFT_32 = (1L << 16) * (1L << 16); public Pbf(byte[] buf) { this.buf = buf != null ? buf : new byte[0]; this.pos = 0; this.type = 0; this.length = this.buf.length; } public T readFields(ReadField readField, T result, int end) { end = end == 0 ? this.length : end; while (this.pos < end) { int val = this.readVarint(); int tag = val >> 3; int startPos = this.pos; this.type = val & 0x7; readField.read(tag, result, this); if (this.pos == startPos) this.skip(val); } return result; } public float readFloat() { float val = Float.intBitsToFloat(readInt32(this.buf, this.pos)); this.pos += 4; return val; } public long readFixed64() { long val = readUInt32(this.buf, this.pos) + ((long) readUInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32); this.pos += 8; return val; } public double readDouble() { double val = Double.longBitsToDouble(readFixed64()); this.pos += 8; return val; } public int readVarint() { return this.readVarint(false); } public int readVarint(boolean isSigned) { final var buf = this.buf; int b = buf[this.pos++]; int val = b & 0x7f; if ((b & 0x80) == 0) return val; b = buf[this.pos++]; val |= (b & 0x7f) << 7; if ((b & 0x80) == 0) return val; b = buf[this.pos++]; val |= (b & 0x7f) << 14; if ((b & 0x80) == 0) return val; b = buf[this.pos++]; val |= (b & 0x7f) << 21; if ((b & 0x80) == 0) return val; b = buf[this.pos]; val |= (b & 0x0f) << 28; return readVarintRemainder(val, isSigned); } private int readVarintRemainder(int val, boolean isSigned) { int b = this.buf[this.pos++]; int high = (b & 0x70) >> 4; if ((b & 0x80) == 0) return toNum(val, high, isSigned); b = this.buf[this.pos++]; high |= (b & 0x7f) << 3; if ((b & 0x80) == 0) return toNum(val, high, isSigned); b = this.buf[this.pos++]; high |= (b & 0x7f) << 10; if ((b & 0x80) == 0) return toNum(val, high, isSigned); b = this.buf[this.pos++]; high |= (b & 0x7f) << 17; if ((b & 0x80) == 0) return toNum(val, high, isSigned); b = this.buf[this.pos++]; high |= (b & 0x7f) << 24; if ((b & 0x80) == 0) return toNum(val, high, isSigned); b = this.buf[this.pos++]; high |= (b & 0x01) << 31; if ((b & 0x80) == 0) return toNum(val, high, isSigned); throw new IllegalArgumentException("Expected varint not more than 10 bytes"); } private static int toNum(int low, int high, boolean isSigned) { if (isSigned) { return (int) (high * 0x100000000L) + (low >>> 0); } return (int) ((high >>> 0) * 0x100000000L) + (low >>> 0); } public long readVarint64() { return this.readVarint(true); } public int readSVarint() { int num = this.readVarint(); return num % 2 == 1 ? (num + 1) / -2 : num / 2; } public boolean readBoolean() { return this.readVarint() != 0; } public String readString() { int end = this.readVarint() + this.pos; int pos = this.pos; this.pos = end; return new String(Arrays.copyOfRange(this.buf, pos, end), StandardCharsets.UTF_8); } public byte[] readBytes() { int end = this.readVarint() + this.pos; byte[] buffer = new byte[end - this.pos]; System.arraycopy(this.buf, this.pos, buffer, 0, buffer.length); this.pos = end; return buffer; } public void skip(int val) { int type = val & 0x7; if (type == Varint) { while (this.buf[this.pos++] > 0x7F) {} } else if (type == Bytes) { this.pos = this.readVarint() + this.pos; } else if (type == Fixed32) { this.pos += 4; } else if (type == Fixed64) { this.pos += 8; } else { throw new IllegalArgumentException("Unimplemented type: " + type); } } private static int readUInt32(byte[] buf, int pos) { return (buf[pos] & 0xFF) | ((buf[pos + 1] & 0xFF) << 8) | ((buf[pos + 2] & 0xFF) << 16) | ((buf[pos + 3] & 0xFF) << 24); } private static int readInt32(byte[] buf, int pos) { return (buf[pos] & 0xFF) | ((buf[pos + 1] & 0xFF) << 8) | ((buf[pos + 2] & 0xFF) << 16) | ((buf[pos + 3] & 0xFF) << 24); } @FunctionalInterface public interface ReadField { void read(int tag, T result, Pbf pbf); } } ================================================ FILE: java/mlt-core/src/main/java/springmeyer/Point.java ================================================ package org.springmeyer; public class Point { public int x; public int y; public Point(int x, int y) { this.x = x; this.y = y; } public Point clone() { return new Point(this.x, this.y); } public String toString() { return "{x:" + this.x + ", y:" + this.y + "}"; } } ================================================ FILE: java/mlt-core/src/main/java/springmeyer/VectorTile.java ================================================ package org.springmeyer; import java.util.Map; /* * This is a java port of https://github.com/mapbox/vector-tile-js */ public class VectorTile { public Map layers; public VectorTile(Pbf pbf, int end) throws IllegalArgumentException { this.layers = pbf.readFields(VectorTile::readTile, new java.util.LinkedHashMap<>(), end); } private static void readTile(int tag, Map layers, Pbf pbf) throws IllegalArgumentException { if (tag == 3) { VectorTileLayer layer = new VectorTileLayer(pbf, pbf.readVarint() + pbf.pos); if (layer.length > 0) { layers.put(layer.name, layer); } } } } ================================================ FILE: java/mlt-core/src/main/java/springmeyer/VectorTileFeature.java ================================================ package org.springmeyer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class VectorTileFeature { public Map properties; public int extent; public int type; public long id; private Pbf pbf; private int geometry; private List keys; private List values; public static final String[] types = {"Unknown", "Point", "LineString", "Polygon"}; @SuppressWarnings("this-escape") public VectorTileFeature(Pbf pbf, int end, int extent, List keys, List values) { this.properties = new HashMap<>(); this.extent = extent; this.type = 0; this.pbf = pbf; this.geometry = -1; this.keys = keys; this.values = values; pbf.readFields(this::readFeature, this, end); } private void readFeature(int tag, VectorTileFeature feature, Pbf pbf) { if (tag == 1) { feature.id = pbf.readVarint(); } else if (tag == 2) { feature.readTag(pbf, feature); } else if (tag == 3) { feature.type = pbf.readVarint(); } else if (tag == 4) { feature.geometry = pbf.pos; } } private void readTag(Pbf pbf, VectorTileFeature feature) { int end = pbf.readVarint() + pbf.pos; while (pbf.pos < end) { String key = feature.keys.get(pbf.readVarint()); Object value = feature.values.get(pbf.readVarint()); feature.properties.put(key, value); } } public List> loadGeometry() { Pbf pbf = this.pbf; pbf.pos = this.geometry; int end = pbf.readVarint() + pbf.pos; int cmd = 1; int length = 0; int x = 0; int y = 0; List> lines = new ArrayList<>(); List line = null; while (pbf.pos < end) { if (length <= 0) { int cmdLen = pbf.readVarint(); cmd = cmdLen & 0x7; length = cmdLen >> 3; } length--; if (cmd == 1 || cmd == 2) { x += pbf.readSVarint(); y += pbf.readSVarint(); if (cmd == 1) { // moveTo if (line != null) lines.add(line); line = new ArrayList<>(); } line.add(new Point(x, y)); } else if (cmd == 7) { // Workaround for https://github.com/mapbox/mapnik-vector-tile/issues/90 if (line != null) { line.add(line.get(0).clone()); // closePolygon } } else { throw new IllegalArgumentException("unknown command " + cmd); } } if (line != null) lines.add(line); return lines; } } ================================================ FILE: java/mlt-core/src/main/java/springmeyer/VectorTileLayer.java ================================================ package org.springmeyer; import java.util.ArrayList; import java.util.List; public class VectorTileLayer { // Public public int version = 1; public String name = null; public int extent = 4096; public int length = 0; // Private private final Pbf pbf; private final List keys = new ArrayList<>(); private final List values = new ArrayList<>(); private final List features = new ArrayList<>(); @SuppressWarnings("this-escape") public VectorTileLayer(Pbf pbf, int end) throws IllegalArgumentException { this.pbf = pbf; pbf.readFields(VectorTileLayer::readLayer, this, end); this.length = this.features.size(); } private static void readLayer(int tag, VectorTileLayer layer, Pbf pbf) throws IllegalArgumentException { switch (tag) { case 15: layer.version = pbf.readVarint(); break; case 1: layer.name = pbf.readString(); break; case 5: layer.extent = pbf.readVarint(); break; case 2: layer.features.add(pbf.pos); break; case 3: layer.keys.add(pbf.readString()); break; case 4: layer.values.add(readValueMessage(pbf)); break; default: break; } } private static Object readValueMessage(Pbf pbf) throws IllegalArgumentException { Object value = null; int end = pbf.readVarint() + pbf.pos; while (pbf.pos < end) { int tag = pbf.readVarint() >> 3; if (tag == 1) { value = pbf.readString(); } else if (tag == 2) { value = pbf.readFloat(); } else if (tag == 3) { value = pbf.readDouble(); } else if (tag == 4) { value = pbf.readVarint64(); } else if (tag == 5) { value = pbf.readVarint(); } else if (tag == 6) { value = pbf.readSVarint(); } else if (tag == 7) { value = pbf.readBoolean(); } } return value; } public VectorTileFeature feature(int i) throws IllegalArgumentException { if (i < 0 || i >= this.features.size()) { throw new IndexOutOfBoundsException("feature index out of bounds"); } this.pbf.pos = this.features.get(i).intValue(); int end = this.pbf.readVarint() + this.pbf.pos; return new VectorTileFeature(this.pbf, end, this.extent, this.keys, this.values); } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/MltGenerator.java ================================================ package org.maplibre.mlt; import static org.maplibre.mlt.TestSettings.ID_REASSIGNABLE_MVT_LAYERS; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.apache.commons.lang3.tuple.Triple; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.maplibre.mlt.converter.ColumnMapping; import org.maplibre.mlt.converter.ColumnMappingConfig; import org.maplibre.mlt.converter.ConversionConfig; import org.maplibre.mlt.converter.FeatureTableOptimizations; import org.maplibre.mlt.converter.MltConverter; import org.maplibre.mlt.converter.encodings.EncodingUtils; import org.maplibre.mlt.converter.mvt.MvtUtils; import org.maplibre.mlt.data.MapboxVectorTile; import org.maplibre.mlt.metadata.tileset.MltMetadata; public class MltGenerator { private static final int MIN_ZOOM = 0; private static final int MAX_ZOOM = 2; private static final String TILESET_METADATA_FILE_NAME = "tileset.pbf"; private static final String MVT_SPECIFIC_TILES_SOURCE_MBTILES = ""; private static final String MVT_SPECIFIC_TILES_SOURCE_DIR = "..\\ts\\test\\data\\omt\\optimized\\mvt"; private static final String MLT_SPECIFIC_TILES_OUTPUT_DIR = "..\\ts\\test\\data\\omt\\optimized\\mlt\\optimized"; private static final String MVT_SPECIFIC_TILES_OUTPUT_DIR = "..\\ts\\test\\data\\omt\\optimized\\mvt"; private static final String MBTILES_FILE = ""; private static final String MVT_OUTPUT_DIR = "..\\test\\data\\optimized\\omt\\mvt"; private static final String MLT_OUTPUT_DIR = "..\\test\\data\\optimized\\omt\\mlt\\plain"; private static final TestUtils.Optimization DEFAULT_OPTIMIZATION = TestUtils.Optimization.NONE; protected static final ColumnMappingConfig COLUMN_MAPPINGS = ColumnMappingConfig.of(Pattern.compile(".*"), List.of(new ColumnMapping("name", "_", true))); private static final boolean DEFAULT_USE_FAST_PFOR = false; private static final boolean DEFAULT_USE_FSST = false; private static final boolean DEFAULT_USE_POLYGON_TESSELLATION = false; private static final boolean DEFAULT_USE_MORTON_ENCODING = false; private static final boolean DEFAULT_INCLUDE_IDS = true; private static final ConversionConfig.TypeMismatchPolicy DEFAULT_MISMATCH_POLICY = ConversionConfig.TypeMismatchPolicy.FAIL; private static final List OUTLINE_POLYGON_FEATURE_TABLE_NAMES = List.of("building"); @Test @Disabled public void generateMltTileset() throws IOException, SQLException, ClassNotFoundException { var mbTilesFilename = "jdbc:sqlite:" + MBTILES_FILE; try (var repo = new MbtilesRepository(mbTilesFilename, MIN_ZOOM, MAX_ZOOM)) { final var optimizations = getOptimizations(); for (var mvTile : repo) { final var tileId = mvTile.tileId(); try { final var isIdPresent = false; final var tileMetadata = MltConverter.createTilesetMetadata(mvTile, COLUMN_MAPPINGS, isIdPresent); final var mlTile = convertMvtToMlt(optimizations, true, mvTile, tileMetadata); final var z = tileId.getLeft(); final var x = tileId.getMiddle(); final var y = (int) Math.pow(2, z) - tileId.getRight() - 1; writeTile(mlTile, MLT_OUTPUT_DIR, ".mlt", x, y, z); final var rawMvTile = repo.getRawTile(tileId); writeTile(rawMvTile, MVT_OUTPUT_DIR, ".mvt", x, y, z); } catch (Exception e) { System.out.println("Error while processing tile " + tileId); e.printStackTrace(); } } } } @Test @Disabled public void generateSpecificMlTiles() throws IOException { var mvtFileNames = Files.list(Paths.get(MVT_SPECIFIC_TILES_SOURCE_DIR)) .filter(file -> !Files.isDirectory(file)) .map(Path::getFileName) .toList(); var fullMvtFileNames = mvtFileNames.stream() .map(f -> Path.of(MVT_SPECIFIC_TILES_SOURCE_DIR, f.toString())) .toList(); var mvTiles = fullMvtFileNames.stream() .map( f -> { try { return MvtUtils.decodeMvt(Files.readAllBytes(f)); } catch (IOException e) { throw new RuntimeException(e); } }) .toList(); var optimizations = getOptimizations(); for (var tileName : mvtFileNames) { var mvt = Files.readAllBytes(Path.of(MVT_SPECIFIC_TILES_SOURCE_DIR, tileName.toString())); var mvTile = MvtUtils.decodeMvt(mvt); try { final var isIdPresent = false; final var tileMetadata = MltConverter.createTilesetMetadata(mvTile, COLUMN_MAPPINGS, isIdPresent); var mlTile = convertMvtToMlt(optimizations, DEFAULT_USE_POLYGON_TESSELLATION, mvTile, tileMetadata); var mltFilename = tileName.toString().replace(".mvt", ".mlt"); Files.write(Path.of(MLT_SPECIFIC_TILES_OUTPUT_DIR, mltFilename), mlTile); } catch (Exception e) { System.out.println("Error while processing tile " + tileName); e.printStackTrace(); } } } @Test @Disabled public void generateSpecificMlTilesFromMbtiles() throws IOException, SQLException, ClassNotFoundException { try (var repo = new MbtilesRepository("jdbc:sqlite:" + MVT_SPECIFIC_TILES_SOURCE_MBTILES, 0, 14)) { var mvTiles = repo.getLargestTilesPerZoom(); var optimizations = getOptimizations(); for (var mvTile : mvTiles) { try { final var isIdPresent = false; final var tileMetadata = MltConverter.createTilesetMetadata(mvTile.getMiddle(), COLUMN_MAPPINGS, isIdPresent); var mlTile = convertMvtToMlt( optimizations, DEFAULT_USE_POLYGON_TESSELLATION, mvTile.getMiddle(), tileMetadata); var tileId = mvTile.getRight(); var tileName = tileId.getLeft() + "_" + tileId.getMiddle() + "_" + tileId.getRight(); Files.write(Path.of(MLT_SPECIFIC_TILES_OUTPUT_DIR, tileName + ".mlt"), mlTile); Files.write(Path.of(MVT_SPECIFIC_TILES_OUTPUT_DIR, tileName + ".mvt"), mvTile.getLeft()); } catch (Exception e) { System.out.println("Error while processing tile " + mvTile); e.printStackTrace(); } } } } private Map getOptimizations() { var allowSorting = DEFAULT_OPTIMIZATION == TestUtils.Optimization.SORTED; // TODO: account for per-layer mappings final var mappings = COLUMN_MAPPINGS.entrySet().stream().flatMap(entry -> entry.getValue().stream()).toList(); var featureTableOptimization = new FeatureTableOptimizations(allowSorting, false, mappings); var optimizations = TestSettings.OPTIMIZED_MVT_LAYERS.stream() .collect(Collectors.toMap(l -> l, l -> featureTableOptimization)); /* Only regenerate the ids for specific layers when the column is not sorted for comparison reasons */ if (DEFAULT_OPTIMIZATION == TestUtils.Optimization.IDS_REASSIGNED) { for (var reassignableLayer : ID_REASSIGNABLE_MVT_LAYERS) { optimizations.put(reassignableLayer, new FeatureTableOptimizations(false, true, mappings)); } } return optimizations; } private ConversionConfig.ConfigBuilder defaultConfigBuilder() { return ConversionConfig.builder() .includeIds(DEFAULT_INCLUDE_IDS) .typeMismatchPolicy(DEFAULT_MISMATCH_POLICY) .useFastPFOR(DEFAULT_USE_FAST_PFOR) .useFSST(DEFAULT_USE_FSST) .useMortonEncoding(DEFAULT_USE_MORTON_ENCODING) .outlineFeatureTableNames(OUTLINE_POLYGON_FEATURE_TABLE_NAMES); } private byte[] convertMvtToMlt( Map optimizations, boolean preTessellatePolygons, MapboxVectorTile mvTile, MltMetadata.TileSetMetadata tileMetadata) throws IOException { final var config = defaultConfigBuilder() .optimizations(optimizations) .preTessellatePolygons(preTessellatePolygons) .build(); return MltConverter.encode(mvTile, tileMetadata, config, null); } private static void writeTile( byte[] tile, String outDir, String tileExtension, int x, int y, int z) throws IOException { var path = Paths.get(outDir, Integer.toString(z), Integer.toString(x), Integer.toString(y)); path.toFile().mkdirs(); var mltFilename = path.resolve(y + tileExtension); System.out.println("Writing tile to " + mltFilename); Files.write(mltFilename, tile); var compressedTile = EncodingUtils.gzip(tile); var compressedTileName = path.resolve(y + tileExtension + ".gz"); Files.write(compressedTileName, compressedTile); } } class MbtilesRepository implements Iterable, Closeable { private static final String TILE_TABLE_NAME = "tiles"; private final Connection connection; private final Statement statement; protected final int minZoom; protected final int maxZoom; MbtilesRepository(String url, int minZoom, int maxZoom) throws ClassNotFoundException, SQLException { Class.forName("org.sqlite.JDBC"); this.connection = DriverManager.getConnection(url); this.statement = this.connection.createStatement(); this.minZoom = minZoom; this.maxZoom = maxZoom; } protected MapboxVectorTile getTile(Triple tileId) { try { var rs = statement.executeQuery( String.format( "SELECT * FROM %s WHERE zoom_level = %d AND" + " tile_column = %d AND tile_row = %d;", TILE_TABLE_NAME, tileId.getLeft(), tileId.getMiddle(), tileId.getRight())); rs.next(); InputStream in = rs.getBinaryStream("tile_data"); byte[] mvt = new byte[in.available()]; in.read(mvt); var uncompressedMvt = EncodingUtils.unzip(mvt); return MvtUtils.decodeMvt(uncompressedMvt); } catch (SQLException | IOException e) { throw new RuntimeException(e); } } protected byte[] getRawTile(Triple tileId) throws SQLException, IOException { var rs = statement.executeQuery( String.format( "SELECT * FROM %s WHERE zoom_level = %d AND" + " tile_column = %d AND tile_row = %d;", TILE_TABLE_NAME, tileId.getLeft(), tileId.getMiddle(), tileId.getRight())); rs.next(); InputStream in = rs.getBinaryStream("tile_data"); byte[] mvt = new byte[in.available()]; in.read(mvt); return EncodingUtils.unzip(mvt); } public List>> getLargestTilesPerZoom() { try { var mvTiles = new ArrayList>>(); for (var zoom = 0; zoom <= maxZoom; zoom++) { var rs = statement.executeQuery( String.format( "SELECT * FROM %s WHERE zoom_level = %d " + "ORDER BY LENGTH(tile_data) DESC LIMIT 1;", TILE_TABLE_NAME, zoom)); rs.next(); InputStream in = rs.getBinaryStream("tile_data"); byte[] mvt = new byte[in.available()]; in.read(mvt); var x = rs.getInt("tile_column"); var y = rs.getInt("tile_row"); var uncompressedMvt = EncodingUtils.unzip(mvt); var decodedMvt = MvtUtils.decodeMvt(uncompressedMvt); var tileId = Triple.of(zoom, x, y); mvTiles.add(Triple.of(uncompressedMvt, decodedMvt, tileId)); } return mvTiles; } catch (SQLException | IOException e) { throw new RuntimeException(e); } } public Queue> getTileIds() { try { // TODO: read in batches to scale also for a planet-scale tileset var rs = statement.executeQuery( String.format( "SELECT * FROM %s WHERE zoom_level >= %d AND zoom_level <= %d", TILE_TABLE_NAME, minZoom, maxZoom)); var tileIds = new LinkedList>(); while (rs.next()) { int z = rs.getInt("zoom_level"); int x = rs.getInt("tile_column"); int y = rs.getInt("tile_row"); var tileId = Triple.of(z, x, y); tileIds.add(tileId); } return tileIds; } catch (SQLException e) { throw new RuntimeException(e); } } public void close() { try { statement.close(); connection.close(); } catch (SQLException e) { throw new RuntimeException(e); } } @NotNull @Override public Iterator iterator() { return new MbtilesIterator(); } private class MbtilesIterator implements Iterator { private Queue> tileIds; @Override public boolean hasNext() { if (tileIds == null) { tileIds = getTileIds(); } return !tileIds.isEmpty(); } @Override public MapboxVectorTile next() { var tileId = tileIds.poll(); var tile = getTile(tileId); tile.setTileId(tileId); return tile; } } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/TestSettings.java ================================================ package org.maplibre.mlt; import java.nio.file.Paths; import java.util.List; public class TestSettings { public static final String OMT_MVT_PATH = Paths.get("..", "..", "test", "fixtures", "omt").toString(); public static final String BING_MVT_PATH = Paths.get("..", "..", "test", "fixtures", "bing").toString(); public static final String AMZ_HERE_MVT_PATH = Paths.get("..", "..", "test", "fixtures", "amazon_here").toString(); public static final List OPTIMIZED_MVT_LAYERS = List.of( "place", "water_name", "transportation", "transportation_name", "park", "mountain_peak", "poi", "waterway", "aerodrome_label", "water_feature", "island", "country_region", "populated_place", "admin_division1", "national_park", "housenumber", "continent", "sov_capital", "road", "water", "landuse", "landcover", "boundary"); /* Layers with ids which are unique per tile but contain no global information, so they can be reassigned * by the converter */ public static final List ID_REASSIGNABLE_MVT_LAYERS = List.of("transportation", "housenumber"); } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/TestUtils.java ================================================ package org.maplibre.mlt; import java.util.Map; import java.util.SequencedCollection; import java.util.stream.Collectors; import org.jetbrains.annotations.NotNull; import org.maplibre.mlt.compare.CompareHelper; import org.maplibre.mlt.data.Feature; import org.maplibre.mlt.data.Layer; import org.maplibre.mlt.data.MVTFeature; import org.maplibre.mlt.data.MapLibreTile; import org.maplibre.mlt.data.MapboxVectorTile; import org.maplibre.mlt.util.StreamUtil; public class TestUtils { public enum Optimization { NONE, SORTED, IDS_REASSIGNED } private static int compareFeatures( SequencedCollection mltFeatures, SequencedCollection mvtFeatures, boolean allowFeatureSort) { final var difference = CompareHelper.compareFeatures( mltFeatures, mvtFeatures, CompareHelper.CompareMode.All, 0, "test", allowFeatureSort); return difference.isPresent() ? 1 : 0; } public static int compareTilesSequential( MapLibreTile mlTile, MapboxVectorTile mvTile, boolean allowFeatureSort) { return StreamUtil.zip( mlTile.getLayerStream(), mvTile.getLayerStream(), (mltLayer, mvtLayer) -> compareFeatures(mltLayer.features(), mvtLayer.features(), allowFeatureSort)) .reduce(0, Integer::sum); } public static interface TileFilter { default boolean test(Layer layer, Feature feature, String propertyKey, Object propertyValue) { return true; } default boolean test(Layer layer) { return true; } default boolean test(Layer layer, Feature feature) { return true; } } // Filter a tile by layer, feature, and/or property. public static MapboxVectorTile filterTile( @NotNull MapboxVectorTile tile, @NotNull TileFilter filter) { return new MapboxVectorTile( tile.getLayerStream() .filter(layer -> filter.test(layer)) .map(layer -> filterLayers(layer, filter)) .toList(), tile.tileId()); } private static @NotNull Layer filterLayers(@NotNull Layer layer, @NotNull TileFilter filter) { return new Layer( layer.name(), layer.features().stream() .filter(feature -> filter.test(layer, feature)) .flatMap(StreamUtil.ofType(MVTFeature.class)) .map(feature -> filterFeatures(layer, feature, filter)) .toList(), layer.tileExtent()); } private static @NotNull Feature filterFeatures( @NotNull Layer layer, @NotNull MVTFeature feature, @NotNull TileFilter filter) { return feature.toBuilder() .properties( feature.getRawProperties().entrySet().stream() .filter(p -> filter.test(layer, feature, p.getKey(), p.getValue())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))) .build(); } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/benchmarks/CompressionBenchmarksTest.java ================================================ package org.maplibre.mlt.benchmarks; import static org.maplibre.mlt.TestSettings.ID_REASSIGNABLE_MVT_LAYERS; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; import org.junit.jupiter.api.Tag; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.locationtech.jts.util.Assert; import org.maplibre.mlt.TestSettings; import org.maplibre.mlt.converter.ColumnMapping; import org.maplibre.mlt.converter.ColumnMappingConfig; import org.maplibre.mlt.converter.ConversionConfig; import org.maplibre.mlt.converter.FeatureTableOptimizations; import org.maplibre.mlt.converter.MltConverter; import org.maplibre.mlt.converter.mvt.MvtUtils; import org.maplibre.mlt.decoder.MltDecoder; /* * Add the tiles which should be benchmarked to the respective directories. * */ @Tag("benchmark") public class CompressionBenchmarksTest { private static final String OMT_PATH = "../../test/fixtures/omt"; public static final String PLACEHOLDER_FILE = ".gitkeep"; @ParameterizedTest @ValueSource(booleans = {false, true}) public void omtCompressionBenchmarks_Sort(boolean sorting) throws IOException { var results = runBenchmarks(OMT_PATH, sorting, List.of(), false); if (results == null) { return; } System.out.printf("Omt sorted tile size reduction: %s%n", results.getMiddle()); System.out.printf("Omt sorted compression ratio: %s%% %n", results.getRight()); System.out.printf("Omt sorted max tile size reduction: %s%n", results.getLeft()); } @ParameterizedTest @ValueSource(booleans = {false, true}) public void omtCompressionBenchmarks_OptimizedIds(boolean tessellate) throws IOException { var results = runBenchmarks(OMT_PATH, true, ID_REASSIGNABLE_MVT_LAYERS, tessellate); if (results == null) { return; } System.out.printf("Omt optimized ids tile size reduction: %s%n", results.getMiddle()); System.out.printf("Omt optimized ids compression ratio: %s%% %n", results.getRight()); System.out.printf("Omt optimized ids max tile size reduction: %s%n", results.getLeft()); } private static Triple runBenchmarks( @SuppressWarnings("SameParameterValue") String path, boolean allowSorting, List reassignableLayers, boolean tessellate) throws IOException { File bingDirectory = new File(path); File[] files = bingDirectory.listFiles(); Assert.isTrue(files != null); var tileSizes = new ArrayList>(); var tiles = Arrays.stream(files) .filter(file -> file.isFile() && !file.getName().equals(PLACEHOLDER_FILE)) .toList(); if (tiles.isEmpty()) { System.out.printf("No tiles found in directory %s\n", path); return null; } for (File tile : tiles) { var tilePath = tile.getAbsolutePath(); var sizes = getBenchmarksAndVerifyTiles(tilePath, allowSorting, reassignableLayers, tessellate); tileSizes.add(sizes); } var totalMltSizes = 0d; var totalMvtSizes = 0d; var maxReduction = 0d; for (var sizes : tileSizes) { totalMltSizes += sizes.getLeft(); totalMvtSizes += sizes.getRight(); var reduction = (1 - (double) sizes.getLeft() / sizes.getRight()) * 100; if (reduction > maxReduction) { maxReduction = reduction; } } var averageMltTileSize = totalMltSizes / tileSizes.size(); var averageMvtTileSize = totalMvtSizes / tileSizes.size(); var averageReduction = (1 - averageMltTileSize / averageMvtTileSize) * 100; var averageRatio = averageMvtTileSize / averageMltTileSize; return Triple.of(maxReduction, averageReduction, averageRatio); } private static Pair getBenchmarksAndVerifyTiles( String tilePath, boolean allowSorting, List reassignableLayers, boolean tessellate) throws IOException { var mvtFilePath = Paths.get(tilePath); var mvTile = MvtUtils.decodeMvt(mvtFilePath); final var columnMapping = new ColumnMapping("name", ":", true); final var columnMappings = ColumnMappingConfig.of(Pattern.compile(".*"), List.of(columnMapping)); final var isIdPresent = true; final var tileMetadata = MltConverter.createTilesetMetadata(mvTile, columnMappings, isIdPresent); var optimization = new FeatureTableOptimizations(allowSorting, false, List.of(columnMapping)); var optimizations = TestSettings.OPTIMIZED_MVT_LAYERS.stream() .collect(Collectors.toMap(l -> l, l -> optimization)); for (var reassignableLayer : reassignableLayers) { optimizations.put( reassignableLayer, new FeatureTableOptimizations(allowSorting, true, List.of(columnMapping))); } final var config = ConversionConfig.builder() .includeIds(true) .useFastPFOR(true) .useFSST(true) .preTessellatePolygons(tessellate) .optimizations(optimizations) .build(); final var mlTile = MltConverter.encode(mvTile, tileMetadata, config, null); if (reassignableLayers.isEmpty()) { /* Only test when the ids are not reassigned since it is verified based on the other tests */ var decodedMlt = MltDecoder.decodeMlTile(mlTile); System.out.println("Vectorized Decoding not implemented"); } var mvtSize = Files.readAllBytes(mvtFilePath).length; return Pair.of(mlTile.length, mvtSize); } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/benchmarks/MltDecoderBenchmarkTest.java ================================================ package org.maplibre.mlt.benchmarks; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.maplibre.mlt.TestSettings; import org.maplibre.mlt.converter.ColumnMapping; import org.maplibre.mlt.converter.ColumnMappingConfig; import org.maplibre.mlt.converter.ConversionConfig; import org.maplibre.mlt.converter.FeatureTableOptimizations; import org.maplibre.mlt.converter.MltConverter; import org.maplibre.mlt.converter.mvt.MvtUtils; import org.maplibre.mlt.decoder.MltDecoder; /** * Quick and dirty benchmarks for the decoding of OpenMapTiles schema based tiles into the MVT and * MLT in-memory representations. Can be used for simple profiling. For more proper benchmarks based * on JMH see `OmtDecoderBenchmark` */ @Tag("benchmark") public class MltDecoderBenchmarkTest { /** * Number of measured iterations. Set via {@code -Dbenchmark.iterations=200} for full benchmarks. * Defaults to 1 for a quick smoke test. When greater than 1, an equal number of warmup iterations * are run before measurement. */ private static final int BENCHMARK_ITERATIONS = Integer.getInteger("benchmark.iterations", 1); @Test public void decodeMlTileVectorized_Z2() throws IOException { var tileId = String.format("%s_%s_%s", 2, 2, 2); benchmarkDecoding(tileId); } @Test public void decodeMlTileVectorized_Z3() throws IOException { var tileId = String.format("%s_%s_%s", 3, 4, 5); benchmarkDecoding(tileId); } @Test public void decodeMlTileVectorized_Z4() throws IOException { var tileId = String.format("%s_%s_%s", 4, 8, 10); benchmarkDecoding(tileId); var tileId2 = String.format("%s_%s_%s", 4, 3, 9); benchmarkDecoding(tileId2); } @Test public void decodeMlTileVectorized_Z5() throws IOException { var tileId = String.format("%s_%s_%s", 5, 16, 21); benchmarkDecoding(tileId); var tileId2 = String.format("%s_%s_%s", 5, 16, 20); benchmarkDecoding(tileId2); } @Test public void decodeMlTileVectorized_Z6() throws IOException { var tileId = String.format("%s_%s_%s", 6, 33, 42); benchmarkDecoding(tileId); } @Test public void decodeMlTileVectorized_Z7() throws IOException { var tileId = String.format("%s_%s_%s", 7, 66, 85); benchmarkDecoding(tileId); } @Test public void decodeMlTileVectorized_Z8() throws IOException { var tileId = String.format("%s_%s_%s", 8, 132, 170); benchmarkDecoding(tileId); } @Test public void decodeMlTileVectorized_Z9() throws IOException { var tileId = String.format("%s_%s_%s", 9, 265, 341); benchmarkDecoding(tileId); } @Test public void decodeMlTileVectorized_Z10() throws IOException { var tileId = String.format("%s_%s_%s", 10, 532, 682); benchmarkDecoding(tileId); } @Test public void decodeMlTileVectorized_Z11() throws IOException { var tileId = String.format("%s_%s_%s", 11, 1064, 1367); benchmarkDecoding(tileId); } @Test public void decodeMlTileVectorized_Z12() throws IOException { var tileId = String.format("%s_%s_%s", 12, 2132, 2734); benchmarkDecoding(tileId); } @Test public void decodeMlTileVectorized_Z13() throws IOException { var tileId = String.format("%s_%s_%s", 13, 4265, 5467); benchmarkDecoding(tileId); } @Test public void decodeMlTileVectorized_Z14() throws IOException { var tileId = String.format("%s_%s_%s", 14, 8298, 10748); benchmarkDecoding(tileId); var tileId2 = String.format("%s_%s_%s", 14, 8299, 10748); benchmarkDecoding(tileId2); } @Test public void benchmarkSuite() throws IOException { System.out.println("Zoom 2 ---------------------------------------"); decodeMlTileVectorized_Z2(); System.out.println("Zoom 3 ---------------------------------------"); decodeMlTileVectorized_Z3(); System.out.println("Zoom 4 ---------------------------------------"); decodeMlTileVectorized_Z4(); System.out.println("Zoom 5 ---------------------------------------"); decodeMlTileVectorized_Z5(); System.out.println("Zoom 6 ---------------------------------------"); decodeMlTileVectorized_Z6(); System.out.println("Zoom 7 ---------------------------------------"); decodeMlTileVectorized_Z7(); System.out.println("Zoom 8 ---------------------------------------"); decodeMlTileVectorized_Z8(); System.out.println("Zoom 9 ---------------------------------------"); decodeMlTileVectorized_Z9(); System.out.println("Zoom 10 ---------------------------------------"); decodeMlTileVectorized_Z10(); System.out.println("Zoom 11 ---------------------------------------"); decodeMlTileVectorized_Z11(); System.out.println("Zoom 12 ---------------------------------------"); decodeMlTileVectorized_Z12(); System.out.println("Zoom 13 ---------------------------------------"); decodeMlTileVectorized_Z13(); System.out.println("Zoom 14 ---------------------------------------"); decodeMlTileVectorized_Z14(); } private void benchmarkDecoding(String tileId) throws IOException { var mvtFilePath = Paths.get(TestSettings.OMT_MVT_PATH, tileId + ".mvt"); int warmup_iters = BENCHMARK_ITERATIONS / 2; var mvt = Files.readAllBytes(mvtFilePath); var mvtTimeElapsed = 0L; for (int i = 0; i < BENCHMARK_ITERATIONS; i++) { long start = System.currentTimeMillis(); var mvTile = MvtUtils.decodeMvtFast(mvt); long finish = System.currentTimeMillis(); if (i > warmup_iters) { mvtTimeElapsed += (finish - start); } } if (BENCHMARK_ITERATIONS > 1) { System.out.println("MVT decoding time: " + (mvtTimeElapsed / (double) warmup_iters)); } else { System.out.println("MVT decoding passed (single iter)"); } var mvTile = MvtUtils.decodeMvt(mvtFilePath); final var columnMapping = new ColumnMapping("name", ":", true); final var columnMappings = ColumnMappingConfig.of(Pattern.compile(".*"), List.of(columnMapping)); final var tileMetadata = MltConverter.createTilesetMetadata(mvTile, columnMappings, true); var allowIdRegeneration = true; var allowSorting = false; var optimization = new FeatureTableOptimizations(allowSorting, allowIdRegeneration, List.of(columnMapping)); // TODO: fix -> either add columMappings per layer or global like when creating the scheme var optimizations = Map.of( "place", optimization, "water_name", optimization, "transportation", optimization, "transportation_name", optimization, "park", optimization, "mountain_peak", optimization, "poi", optimization, "waterway", optimization, "aerodrome_label", optimization); var mlTile = MltConverter.encode( mvTile, tileMetadata, ConversionConfig.builder() .includeIds(true) .useFastPFOR(true) .useFSST(true) .optimizations(optimizations) .build(), null); var mltTimeElapsed = 0L; for (int i = 0; i < BENCHMARK_ITERATIONS; i++) { long start = System.currentTimeMillis(); var decodedTile = MltDecoder.decodeMlTile(mlTile); long finish = System.currentTimeMillis(); if (i > warmup_iters) { mltTimeElapsed += (finish - start); } } if (BENCHMARK_ITERATIONS > 1) { System.out.println("MLT decoding time: " + (mltTimeElapsed / (double) warmup_iters)); } else { System.out.println("MLT decoding passed (single iter)"); } } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/compare/CompareHelperTest.java ================================================ package org.maplibre.mlt.compare; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; import org.maplibre.mlt.compare.CompareHelper.CompareMode; import org.maplibre.mlt.data.Layer; import org.maplibre.mlt.data.MVTFeature; import org.maplibre.mlt.data.MapLibreTile; import org.maplibre.mlt.data.MapboxVectorTile; class CompareHelperTest { @Test void identicalEmptyTilesAreEqual() { final var result = CompareHelper.compareTiles(mltOf(), mvtOf(), CompareMode.All); assertFalse(result.isPresent()); } @Test void identicalTilesWithFeaturesAreEqual() { final var layer = createLayer("roads", createPointFeature(1, Map.of("name", "Main St"))); final var result = CompareHelper.compareTiles(mltOf(layer), mvtOf(layer), CompareMode.All); assertFalse(result.isPresent()); } @Test void identicalTilesWithMultipleLayersAreEqual() { final var l1 = createLayer("roads", createPointFeature(1, Map.of("name", "Main St"))); final var l2 = createLayer("water", createPointFeature(2, Map.of("class", "river"))); final var result = CompareHelper.compareTiles(mltOf(l1, l2), mvtOf(l1, l2), CompareMode.All); assertFalse(result.isPresent()); } @Test void differentLayerCountIsDetected() { final var mlt = mltOf(createLayer("roads", createPointFeature(1, Map.of()))); final var mvt = mvtOf( createLayer("roads", createPointFeature(1, Map.of())), createLayer("water", createPointFeature(2, Map.of()))); final var result = CompareHelper.compareTiles(mlt, mvt, CompareMode.All); assertTrue(result.isPresent()); assertTrue(result.get().toString().contains("Number of layers")); } @Test void emptyMvtLayersAreIgnoredInLayerCount() { final var emptyLayer = createLayer("empty"); final var roadsLayer = createLayer("roads", createPointFeature(1, Map.of())); final var mvt = mvtOf(emptyLayer, roadsLayer); final var mlt = mltOf(roadsLayer); final var result = CompareHelper.compareTiles(mlt, mvt, CompareMode.All); assertFalse(result.isPresent()); } @Test void differentLayerNames() { final var mlt = mltOf(createLayer("roads", createPointFeature(1, Map.of()))); final var mvt = mvtOf(createLayer("water", createPointFeature(1, Map.of()))); final var result = CompareHelper.compareTiles(mlt, mvt, CompareMode.All); assertTrue(result.isPresent()); assertTrue(result.get().toString().contains("Layer names differ")); } @Test void differentFeatureCountInLayer() { final var mlt = mltOf( createLayer("roads", createPointFeature(1, Map.of()), createPointFeature(2, Map.of()))); final var mvt = mvtOf(createLayer("roads", createPointFeature(1, Map.of()))); final var result = CompareHelper.compareTiles(mlt, mvt, CompareMode.All); assertTrue(result.isPresent()); assertTrue(result.get().toString().contains("Number of features differ")); } @Test void differentFeatureIds() { final var mlt = mltOf(createLayer("roads", createPointFeature(99, Map.of()))); final var mvt = mvtOf(createLayer("roads", createPointFeature(1, Map.of()))); final var result = CompareHelper.compareTiles(mlt, mvt, CompareMode.All); assertTrue(result.isPresent()); assertTrue(result.get().toString().contains("Feature IDs differ")); } @Test void featureWithIdVsFeatureWithoutIdDiffer() { final var withId = createLayer("roads", createPointFeature(1, Map.of())); final var withoutId = createLayer("roads", createPointFeature(Map.of())); final var result = CompareHelper.compareTiles(mltOf(withoutId), mvtOf(withId), CompareMode.All); assertTrue(result.isPresent()); } @Test void featuresWithoutIdAreEqual() { final var withId = createLayer("roads", createPointFeature(Map.of())); final var withoutId = createLayer("roads", createPointFeature(Map.of())); final var result = CompareHelper.compareTiles(mltOf(withoutId), mvtOf(withId), CompareMode.All); assertFalse(result.isPresent()); } @Test void differentGeometriesInGeometryMode() { final var point1 = FACTORY.createPoint(new Coordinate(1, 2)); final var point2 = FACTORY.createPoint(new Coordinate(3, 4)); final var mlt = mltOf(createLayer("roads", createFeature(1, point1))); final var mvt = mvtOf(createLayer("roads", createFeature(1, point2))); final var result = CompareHelper.compareTiles(mlt, mvt, CompareMode.Geometry); assertTrue(result.isPresent()); assertTrue(result.get().toString().contains("Geometries do not match")); } @Test void geometryDifferencesAreNotCheckedInLayersOnlyMode() { final var point1 = FACTORY.createPoint(new Coordinate(1, 2)); final var point2 = FACTORY.createPoint(new Coordinate(3, 4)); final var mlt = mltOf(createLayer("roads", createFeature(1, point1))); final var mvt = mvtOf(createLayer("roads", createFeature(1, point2))); final var result = CompareHelper.compareTiles(mlt, mvt, CompareMode.Layers); assertFalse(result.isPresent()); } @Test void geometryDifferencesAreNotCheckedInPropertiesMode() { final var point1 = FACTORY.createPoint(new Coordinate(1, 2)); final var point2 = FACTORY.createPoint(new Coordinate(3, 4)); final var mlt = mltOf(createLayer("roads", createFeature(1, point1))); final var mvt = mvtOf(createLayer("roads", createFeature(1, point2))); final var result = CompareHelper.compareTiles(mlt, mvt, CompareMode.Properties); assertFalse(result.isPresent()); } @Test void differentPropertyKeys() { final var mlt = mltOf(createLayer("roads", createPointFeature(1, Map.of("name", "Main St")))); final var mvt = mvtOf(createLayer("roads", createPointFeature(1, Map.of("ref", "M1")))); final var result = CompareHelper.compareTiles(mlt, mvt, CompareMode.Properties); assertTrue(result.isPresent()); assertTrue(result.get().toString().contains("Property keys do not match")); } @Test void differentPropertyValues() { final var mlt = mltOf(createLayer("roads", createPointFeature(1, Map.of("name", "Side St")))); final var mvt = mvtOf(createLayer("roads", createPointFeature(1, Map.of("name", "Main St")))); final var result = CompareHelper.compareTiles(mlt, mvt, CompareMode.Properties); assertTrue(result.isPresent()); assertTrue(result.get().toString().contains("Property values do not match")); } @Test void propertyDifferencesNotCheckedInGeometryMode() { final var mlt = mltOf(createLayer("roads", createPointFeature(1, Map.of("name", "Side St")))); final var mvt = mvtOf(createLayer("roads", createPointFeature(1, Map.of("name", "Main St")))); final var result = CompareHelper.compareTiles(mlt, mvt, CompareMode.Geometry); assertFalse(result.isPresent()); } @Test void nullPropertyValueInMltTreatedAsAbsent() { final Map mltProps = new java.util.HashMap<>(); mltProps.put("name", null); final var mlt = mltOf(createLayer("roads", createPointFeature(1, mltProps))); final var mvt = mvtOf(createLayer("roads", createPointFeature(1, Map.of()))); final var result = CompareHelper.compareTiles(mlt, mvt, CompareMode.Properties); assertFalse(result.isPresent()); } @Test void numericValuesWithSameStringRepresentationEqual() { final var mltFeature = createPointFeature(1, Map.of("pop", 42L)); final var mvtFeature = createPointFeature(1, Map.of("pop", 42)); final var mlt = mltOf(createLayer("roads", mltFeature)); final var mvt = mvtOf(createLayer("roads", mvtFeature)); final var result = CompareHelper.compareTiles(mlt, mvt, CompareMode.Properties); assertFalse(result.isPresent()); } @Test void layerFilterIncludesOnlyMatchingLayers() { final var roadsMlt = createLayer("roads", createPointFeature(1, Map.of("name", "Main St"))); final var roadsMvt = createLayer("roads", createPointFeature(1, Map.of("name", "Main St"))); final var waterMvt = createLayer("water", createPointFeature(2, Map.of("name", "wrong"))); final var waterMlt = createLayer("water", createPointFeature(2, Map.of("name", "wrong"))); final var result = CompareHelper.compareTiles( mltOf(roadsMlt, waterMlt), mvtOf(roadsMvt, waterMvt), CompareMode.All, Pattern.compile("roads"), false); assertFalse(result.isPresent()); } @Test void layerFilterExcludesMatchingLayersWhenInverted() { final var roadsMlt = createLayer("roads", createPointFeature(1, Map.of("name", "Main St"))); final var roadsMvt = createLayer("roads", createPointFeature(1, Map.of("name", "Main St"))); final var waterMvt = createLayer("water", createPointFeature(2, Map.of("name", "wrong"))); final var waterMlt = createLayer("water", createPointFeature(2, Map.of("name", "wrong"))); final var result = CompareHelper.compareTiles( mltOf(roadsMlt, waterMlt), mvtOf(roadsMvt, waterMvt), CompareMode.All, Pattern.compile("water"), true); assertFalse(result.isPresent()); } @Test void layerFilterDetectsDifferenceInMatchingLayer() { final var roadsMlt = createLayer("roads", createPointFeature(1, Map.of("name", "Side St"))); final var roadsMvt = createLayer("roads", createPointFeature(1, Map.of("name", "Main St"))); final var mlt = mltOf(roadsMlt); final var mvt = mvtOf(roadsMvt); final var result = CompareHelper.compareTiles(mlt, mvt, CompareMode.All, Pattern.compile("roads"), false); assertTrue(result.isPresent()); } @Test void nullLayerFilterComparesAllLayers() { final var l1 = createLayer("roads", createPointFeature(1, Map.of("name", "Main St"))); final var l2 = createLayer("water", createPointFeature(2, Map.of())); final var result = CompareHelper.compareTiles(mltOf(l1, l2), mvtOf(l1, l2), CompareMode.All, null, false); assertFalse(result.isPresent()); } @Test void differenceMessageIncludesLayerName() { final var mlt = mltOf(createLayer("roads", createPointFeature(1, Map.of("x", "a")))); final var mvt = mvtOf(createLayer("roads", createPointFeature(1, Map.of("x", "b")))); final var diff = CompareHelper.compareTiles(mlt, mvt, CompareMode.Properties); assertTrue(diff.isPresent()); assertTrue(diff.get().toString().contains("roads")); } @Test void differenceMessageIncludesFeatureIndex() { final var mlt = mltOf( createLayer( "roads", createPointFeature(1, Map.of("x", "ok"), 0), createPointFeature(2, Map.of("x", "bad"), 1))); final var mvt = mvtOf( createLayer( "roads", createPointFeature(2, Map.of("x", "good"), 1), createPointFeature(1, Map.of("x", "ok"), 0))); // Features sorted by id, difference at index 1 final var diff = CompareHelper.compareTiles(mlt, mvt, CompareMode.Properties); assertTrue(diff.isPresent()); assertTrue(diff.get().toString().contains("1")); } @Test void featuresWithIdsMatchEvenWhenOrderDiffers() { final var mlt = mltOf( createLayer( "roads", createPointFeature(1, Map.of("name", "first")), createPointFeature(2, Map.of("name", "second")))); final var mvt = mvtOf( createLayer( "roads", createPointFeature(2, Map.of("name", "second")), createPointFeature(1, Map.of("name", "first")))); final var result = CompareHelper.compareTiles(mlt, mvt, CompareMode.All); assertFalse(result.isPresent()); } @Test void featuresWithoutIdsDoNotMatchWhenOrderDiffers() { final var mlt = mltOf( createLayer( "roads", createPointFeature(Map.of("name", "first")), createPointFeature(Map.of("name", "second")))); final var mvt = mvtOf( createLayer( "roads", createPointFeature(Map.of("name", "second")), createPointFeature(Map.of("name", "first")))); final var result = CompareHelper.compareTiles(mlt, mvt, CompareMode.Properties); assertTrue(result.isPresent()); } @Test void sortingByIdStillDetectsPropertyDifferences() { final var mlt = mltOf( createLayer( "roads", createPointFeature(1, Map.of("name", "first")), createPointFeature(2, Map.of("name", "second")))); final var mvt = mvtOf( createLayer( "roads", createPointFeature(2, Map.of("name", "wrong")), createPointFeature(1, Map.of("name", "first")))); final var result = CompareHelper.compareTiles(mlt, mvt, CompareMode.Properties); assertTrue(result.isPresent()); } private static MapLibreTile mltOf(@NotNull Layer... layers) { return new MapLibreTile(List.of(layers)); } private static MapboxVectorTile mvtOf(@NotNull Layer... layers) { return new MapboxVectorTile(List.of(layers)); } private static Layer createLayer(@NotNull String name, @NotNull MVTFeature... features) { return new Layer(name, List.of(features), 4096); } private static MVTFeature createPointFeature(long id, Map props) { return createPointFeature(id, props, 0); } private static MVTFeature createPointFeature(long id, Map props, int index) { return MVTFeature.builder() .index(index) .id(id) .geometry(FACTORY.createPoint(new Coordinate(1, 2))) .properties(props) .build(); } private static MVTFeature createFeature(long id, Geometry geom) { return createFeature(id, geom, 0); } private static MVTFeature createFeature(long id, Geometry geom, int index) { return MVTFeature.builder().index(index).id(id).geometry(geom).properties(Map.of()).build(); } private static MVTFeature createPointFeature(Map props) { return createPointFeature(props, 0); } private static MVTFeature createPointFeature(Map props, int index) { return MVTFeature.builder() .index(index) .geometry(FACTORY.createPoint(new Coordinate(1, 2))) .properties(props) .build(); } private static final GeometryFactory FACTORY = new GeometryFactory(); } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/converter/ConversionConfigTest.java ================================================ package org.maplibre.mlt.converter; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.junit.jupiter.api.Test; public class ConversionConfigTest { private static Map sampleOptimizations() { var m = new HashMap(); m.put("layerA", new FeatureTableOptimizations(true, false, List.of())); return m; } private static void assertConfigEquals(ConversionConfig expected, ConversionConfig actual) { assertEquals(expected.includeIds(), actual.includeIds(), "includeIds"); assertEquals(expected.useFastPFOR(), actual.useFastPFOR(), "useFastPFOR"); assertEquals(expected.useFSST(), actual.useFSST(), "useFSST"); assertEquals(expected.typeMismatchPolicy(), actual.typeMismatchPolicy(), "typeMismatchPolicy"); assertEquals(expected.optimizations(), actual.optimizations(), "optimizations"); assertEquals(expected.useMortonEncoding(), actual.useMortonEncoding(), "useMortonEncoding"); assertEquals( expected.preTessellatePolygons(), actual.preTessellatePolygons(), "preTessellatePolygons"); assertEquals(expected.outlineFeatureTableNames(), actual.outlineFeatureTableNames(), "outline"); var expPattern = expected.layerFilterPattern(); var actPattern = actual.layerFilterPattern(); if (expPattern == null) { assertNull(actPattern, "layerFilterPattern"); } else { assertNotNull(actPattern, "layerFilterPattern-not-null"); assertEquals(expPattern.pattern(), actPattern.pattern(), "layerFilterPattern.value"); } assertEquals(expected.layerFilterInvert(), actual.layerFilterInvert(), "layerFilterInvert"); assertEquals( expected.integerEncodingOption(), actual.integerEncodingOption(), "integerEncoding"); assertEquals( expected.geometryEncodingOption(), actual.geometryEncodingOption(), "geometryEncoding"); } @Test public void testDefaults_fromNoArgConstructor() { var cfg = ConversionConfig.builder().build(); assertEquals(ConversionConfig.DEFAULT_INCLUDE_IDS, cfg.includeIds()); assertEquals(ConversionConfig.DEFAULT_USE_FAST_PFOR, cfg.useFastPFOR()); assertEquals(ConversionConfig.DEFAULT_USE_FSST, cfg.useFSST()); assertEquals(ConversionConfig.DEFAULT_MISMATCH_POLICY, cfg.typeMismatchPolicy()); assertNotNull(cfg.optimizations(), "optimizations-not-null"); assertTrue(cfg.optimizations().isEmpty(), "optimizations-empty"); assertEquals(ConversionConfig.DEFAULT_USE_MORTON_ENCODING, cfg.useMortonEncoding()); assertEquals(ConversionConfig.DEFAULT_PRE_TESSELLATE_POLYGONS, cfg.preTessellatePolygons()); assertNotNull(cfg.outlineFeatureTableNames(), "outline-not-null"); assertTrue(cfg.outlineFeatureTableNames().isEmpty(), "outline-empty"); assertNull(cfg.layerFilterPattern(), "layerFilterPattern-default-null"); assertEquals(ConversionConfig.DEFAULT_LAYER_FILTER_INVERT, cfg.layerFilterInvert()); assertEquals(ConversionConfig.DEFAULT_INTEGER_ENCODING, cfg.integerEncodingOption()); assertEquals(ConversionConfig.DEFAULT_INTEGER_ENCODING, cfg.geometryEncodingOption()); } @Test public void testConstructor_preservesPassedOptimizationsAndOutline() { var optim = sampleOptimizations(); var outline = List.of("layerA"); var cfg = ConversionConfig.builder() .includeIds(true) .useFastPFOR(false) .useFSST(false) .typeMismatchPolicy(ConversionConfig.TypeMismatchPolicy.ELIDE) .optimizations(optim) .preTessellatePolygons(false) .useMortonEncoding(true) .outlineFeatureTableNames(outline) .layerFilterPattern(Pattern.compile("layerA")) .layerFilterInvert(false) .integerEncodingOption(ConversionConfig.IntegerEncodingOption.PLAIN) .geometryEncodingOption(ConversionConfig.IntegerEncodingOption.PLAIN) .build(); assertSame(optim, cfg.optimizations(), "optimizations-same-reference"); assertEquals(outline, cfg.outlineFeatureTableNames(), "outline-equals"); } @Test public void testBuilder_setsAllFields_andBuildProducesEquivalentConfig() { var optim = sampleOptimizations(); var outline = List.of("layerA"); var pattern = Pattern.compile("^layer.*$"); var built = ConversionConfig.builder() .includeIds(false) .useFastPFOR(true) .useFSST(true) .typeMismatchPolicy(ConversionConfig.TypeMismatchPolicy.COERCE) .optimizations(optim) .preTessellatePolygons(true) .useMortonEncoding(false) .outlineFeatureTableNames(outline) .layerFilterPattern(pattern) .layerFilterInvert(true) .integerEncodingOption(ConversionConfig.IntegerEncodingOption.DELTA) .build(); assertEquals(false, built.includeIds()); assertEquals(true, built.useFastPFOR()); assertEquals(true, built.useFSST()); assertEquals(ConversionConfig.TypeMismatchPolicy.COERCE, built.typeMismatchPolicy()); assertEquals(optim, built.optimizations()); assertEquals(true, built.preTessellatePolygons()); assertEquals(false, built.useMortonEncoding()); assertEquals(outline, built.outlineFeatureTableNames()); assertNotNull(built.layerFilterPattern()); assertEquals(pattern.pattern(), built.layerFilterPattern().pattern()); assertEquals(true, built.layerFilterInvert()); assertEquals(ConversionConfig.IntegerEncodingOption.DELTA, built.integerEncodingOption()); } @Test public void testAsBuilder_roundTrip() { var orig = ConversionConfig.builder() .includeIds(true) .useFastPFOR(true) .useFSST(false) .typeMismatchPolicy(ConversionConfig.TypeMismatchPolicy.ELIDE) .optimizations(sampleOptimizations()) .preTessellatePolygons(false) .useMortonEncoding(true) .outlineFeatureTableNames(List.of("layerA")) .layerFilterPattern(Pattern.compile("layerA")) .layerFilterInvert(false) .integerEncodingOption(ConversionConfig.IntegerEncodingOption.PLAIN) .build(); var rebuilt = orig.toBuilder().build(); assertConfigEquals(orig, rebuilt); } @Test public void testLayerFilterPatternAndInvert_behavior() { var pattern = Pattern.compile("layerA"); var cfg = ConversionConfig.builder().layerFilterPattern(pattern).layerFilterInvert(true).build(); assertNotNull(cfg.layerFilterPattern()); assertEquals("layerA", cfg.layerFilterPattern().pattern()); assertTrue(cfg.layerFilterInvert()); var cfg2 = ConversionConfig.builder().layerFilterPattern(null).layerFilterInvert(false).build(); assertNull(cfg2.layerFilterPattern()); assertFalse(cfg2.layerFilterInvert()); } @Test public void testIntegerEncodingOption_defaultsAndCustom() { var defaultCfg = ConversionConfig.builder().build(); assertEquals(ConversionConfig.DEFAULT_INTEGER_ENCODING, defaultCfg.integerEncodingOption()); var custom = ConversionConfig.builder() .integerEncodingOption(ConversionConfig.IntegerEncodingOption.RLE) .build(); assertEquals(ConversionConfig.IntegerEncodingOption.RLE, custom.integerEncodingOption()); } @Test public void testGeometryEncodingOption_defaultsAndCustom() { var defaultCfg = ConversionConfig.builder().build(); assertEquals(ConversionConfig.DEFAULT_INTEGER_ENCODING, defaultCfg.geometryEncodingOption()); var custom = ConversionConfig.builder() .geometryEncodingOption(ConversionConfig.IntegerEncodingOption.PLAIN) .build(); assertEquals(ConversionConfig.IntegerEncodingOption.PLAIN, custom.geometryEncodingOption()); // When only integer encoding is set, geometry stays AUTO (backward compatible with main: // on main, geometry streams always used AUTO and were not controlled by integerEncodingOption). var withIntegerOnly = ConversionConfig.builder() .integerEncodingOption(ConversionConfig.IntegerEncodingOption.RLE) .build(); assertEquals( ConversionConfig.IntegerEncodingOption.RLE, withIntegerOnly.integerEncodingOption()); assertEquals( ConversionConfig.DEFAULT_INTEGER_ENCODING, withIntegerOnly.geometryEncodingOption()); } @Test public void testIntegerEncodingOnlyConstructor_geometryStaysAuto() { // Constructor that takes integerEncodingOption: geometry encoding stays AUTO for backward // compatibility (on main, geometry was never configurable and always used AUTO). var cfg = ConversionConfig.builder() .includeIds(true) .useFastPFOR(false) .useFSST(false) .optimizations(Map.of()) .preTessellatePolygons(false) .integerEncodingOption(ConversionConfig.IntegerEncodingOption.DELTA) .build(); assertEquals(ConversionConfig.IntegerEncodingOption.DELTA, cfg.integerEncodingOption()); assertEquals(ConversionConfig.DEFAULT_INTEGER_ENCODING, cfg.geometryEncodingOption()); } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/converter/MltConverterTest.java ================================================ package org.maplibre.mlt.converter; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.List; import java.util.Map; import java.util.Optional; import org.junit.jupiter.api.Test; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; import org.maplibre.mlt.data.Layer; import org.maplibre.mlt.data.MVTFeature; import org.maplibre.mlt.data.MapboxVectorTile; import org.maplibre.mlt.metadata.tileset.MltMetadata; class MltConverterTest { @Test void coerceValuesToString() { var metadata = MltConverter.createTilesetMetadata( createTileWithMixedTypes(), new ColumnMappingConfig(), true, ConversionConfig.TypeMismatchPolicy.COERCE); var column = metadata.featureTables.stream() .flatMap(it -> it.columns().stream()) .filter(it -> "key".equals(it.getName())) .findFirst() .get(); assertEquals(MltMetadata.ScalarType.STRING, column.field().type().scalarType().physicalType()); } @Test void elideValues() { var metadata = MltConverter.createTilesetMetadata( createTileWithMixedTypes(), new ColumnMappingConfig(), true, ConversionConfig.TypeMismatchPolicy.ELIDE); var column = metadata.featureTables.stream() .flatMap(it -> it.columns().stream()) .filter(it -> "key".equals(it.getName())) .findFirst() .get(); assertEquals(MltMetadata.ScalarType.DOUBLE, column.field().type().scalarType().physicalType()); } @Test void failOnMismatchedValues() { assertThrows( RuntimeException.class, () -> { MltConverter.createTilesetMetadata( createTileWithMixedTypes(), new ColumnMappingConfig(), true); }); } private static MapboxVectorTile createTileWithMixedTypes() { return new MapboxVectorTile( List.of( new Layer( "layer", List.of( MVTFeature.builder() .index(0) .id(1) .geometry(emptyGeometry) .properties(Map.of("key", 1.2)) .build(), MVTFeature.builder() .index(1) .id(2) .geometry(emptyGeometry) .properties(Map.of("key", "2")) .build(), MVTFeature.builder() .index(2) .id(3) .geometry(emptyGeometry) .properties(Map.of("key", 1)) .build()), 4096))); } @Test void generateMetadataJSON() { final var metadata = new MltMetadata.TileSetMetadata(); metadata.name = "test"; metadata.description = "A test tileset"; metadata.attribution = "MapLibre"; metadata.bounds = List.of(-180.0, -85.0511287798066, 180.0, 85.0511287798066); metadata.center = List.of(0.0, 0.0); metadata.minZoom = Optional.of(0); metadata.maxZoom = Optional.of(14); metadata.featureTables = List.of(); MltConverter.createTilesetMetadataJSON(metadata); } private static final GeometryFactory factory = new GeometryFactory(); private static final Geometry emptyGeometry = factory.createEmpty(2); } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/converter/encodings/EncodingUtilsTest.java ================================================ package org.maplibre.mlt.converter.encodings; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import java.io.IOException; import java.util.BitSet; import java.util.List; import java.util.stream.IntStream; import me.lemire.integercompression.IntWrapper; import org.junit.jupiter.api.Test; import org.maplibre.mlt.decoder.DecodingUtils; public class EncodingUtilsTest { @Test public void encodeRle_MixedRunsAndLiterals_ValidEncoding() { var values = List.of(1, 1, 1, 2, 4, 5, 8, 8, 8, 8, 9, 9); var expectedRuns = List.of(3, 1, 1, 1, 4, 2); var expectedValues = List.of(1, 2, 4, 5, 8, 9); var actualValues = EncodingUtils.encodeRle(values.stream().mapToInt(i -> i).toArray()); assertEquals(expectedRuns, IntStream.of(actualValues.getLeft()).boxed().toList()); assertEquals(expectedValues, IntStream.of(actualValues.getRight()).boxed().toList()); } @Test public void encodeRle_OnlyLiterals_ValidEncoding() { var values = List.of(1, 2, 3, 4, 5, 6, 7, 8); var expectedRuns = List.of(1, 1, 1, 1, 1, 1, 1, 1); var expectedValues = List.of(1, 2, 3, 4, 5, 6, 7, 8); var actualValues = EncodingUtils.encodeRle(values.stream().mapToInt(i -> i).toArray()); assertEquals(expectedRuns, IntStream.of(actualValues.getLeft()).boxed().toList()); assertEquals(expectedValues, IntStream.of(actualValues.getRight()).boxed().toList()); } @Test public void encodeRle_OnlyRuns_ValidEncoding() { var values = List.of(10, 10, 10, 10, 20, 20, 40, 40, 40, 40); var expectedRuns = List.of(4, 2, 4); var expectedValues = List.of(10, 20, 40); var actualValues = EncodingUtils.encodeRle(values.stream().mapToInt(i -> i).toArray()); assertEquals(expectedRuns, IntStream.of(actualValues.getLeft()).boxed().toList()); assertEquals(expectedValues, IntStream.of(actualValues.getRight()).boxed().toList()); } @Test public void encodeBooleanRle() throws IOException { var numValues = 70; var bitset = new BitSet(); for (var i = 0; i < numValues; i++) { bitset.set(i, false); } var encodedBooleans = EncodingUtils.encodeBooleanRle(bitset, numValues); var decodeBooleans = DecodingUtils.decodeBooleanRle( encodedBooleans, numValues, encodedBooleans.length, new IntWrapper(0)); for (var i = 0; i < numValues; i++) { assertFalse(decodeBooleans.get(i)); } } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/converter/encodings/LinearRegressionTest.java ================================================ package org.maplibre.mlt.converter.encodings; import static org.maplibre.mlt.converter.encodings.LinearRegression.calculateDeltas; import static org.maplibre.mlt.converter.encodings.LinearRegression.gradientDescent; import java.io.IOException; import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; import java.util.stream.Collectors; import java.util.stream.DoubleStream; import java.util.stream.IntStream; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.maplibre.mlt.TestSettings; import org.maplibre.mlt.converter.geometry.HilbertCurve; import org.maplibre.mlt.converter.geometry.Vertex; import org.maplibre.mlt.converter.mvt.MvtUtils; public class LinearRegressionTest { @Test public void test() throws IOException { var tileId = String.format("%s_%s_%s", 5, 16, 20); var mvtFilePath = Paths.get(TestSettings.OMT_MVT_PATH, tileId + ".mvt"); var mvTile = MvtUtils.decodeMvt(mvtFilePath); for (var layer : mvTile.getLayers()) { if (layer.name().equals("transportation")) { var features = layer.features(); var geometries = features.stream().map(f -> f.getGeometry()).collect(Collectors.toList()); var vertices = geometries.stream() .map(g -> g.getCoordinates()) .flatMap(i -> Stream.of(i[0], i[1])) .collect(Collectors.toList()); var minVertexValue = Collections.min( vertices.stream() .flatMapToDouble(v -> DoubleStream.of(v.getX(), v.getY())) .boxed() .collect(Collectors.toList())); var maxVertexValue = Collections.max( vertices.stream() .flatMapToDouble(v -> DoubleStream.of(v.getX(), v.getY())) .boxed() .collect(Collectors.toList())); var hilbertCurve = new HilbertCurve(minVertexValue.intValue(), maxVertexValue.intValue()); var hilbertIndices = vertices.stream() .mapToInt(i -> hilbertCurve.encode(new Vertex((int) i.getX(), (int) i.getY()))) .toArray(); var sortedHilbertIndices = Arrays.stream(hilbertIndices).sorted().boxed().collect(Collectors.toList()); sortedHilbertIndices = sortedHilbertIndices.stream().distinct().collect(Collectors.toList()).subList(82, 99); var deltas = EncodingUtils.encodeDeltas(sortedHilbertIndices.stream().mapToInt(i -> i).toArray()); double[] indices = IntStream.range(0, deltas.length).boxed().mapToDouble(i -> i).toArray(); double alpha = 0.01; // example learning rate int iterations = 1000; // example number of iterations double[] J = new double[iterations]; // to store cost history double[] theta = gradientDescent( indices, sortedHilbertIndices.stream().mapToDouble(i -> i).toArray(), alpha, iterations, J); var deltasLinearRegression = calculateDeltas( indices, sortedHilbertIndices.stream().mapToDouble(i -> i).toArray(), theta); var modifiedDeltasLinearRegression = Arrays.copyOfRange(deltasLinearRegression, 1, deltasLinearRegression.length); var modifiedDeltas = Arrays.copyOfRange(deltas, 1, deltas.length); var deltaSum = Arrays.stream(modifiedDeltas).sum(); var deltaLinearRegressionSum = Arrays.stream(modifiedDeltasLinearRegression).sum(); System.out.println( "delta: " + deltaSum + " linear Regression delta: " + deltaLinearRegressionSum); System.out.println( "max delta: " + Arrays.stream(modifiedDeltas).max().getAsInt() + " max linear Regression delta: " + Arrays.stream(modifiedDeltasLinearRegression).max().getAsDouble()); } } } @Test public void test2() throws IOException { var tileId = String.format("%s_%s_%s", 5, 16, 20); var mvtFilePath = Paths.get(TestSettings.OMT_MVT_PATH, tileId + ".mvt"); var mvTile = MvtUtils.decodeMvt(mvtFilePath); for (var layer : mvTile.getLayers()) { if (layer.name().equals("transportation")) { var features = layer.features(); var geometries = features.stream().map(f -> f.getGeometry()).collect(Collectors.toList()); var vertices = geometries.stream() .map(g -> g.getCoordinates()) .flatMap(i -> Stream.of(i[0], i[1])) .collect(Collectors.toList()); var xCoordinates = vertices.stream() .mapToInt(i -> (int) i.getX()) .distinct() .boxed() .collect(Collectors.toList()) .subList(81, 104); var deltas = EncodingUtils.encodeDeltas(xCoordinates.stream().mapToInt(i -> i).toArray()); double[] indices = IntStream.range(0, deltas.length).boxed().mapToDouble(i -> i).toArray(); double alpha = 0.01; // example learning rate int iterations = 1000; // example number of iterations double[] J = new double[iterations]; // to store cost history double[] theta = gradientDescent( indices, xCoordinates.stream().mapToDouble(i -> i).toArray(), alpha, iterations, J); var deltasLinearRegression = calculateDeltas(indices, xCoordinates.stream().mapToDouble(i -> i).toArray(), theta); var modifiedDeltasLinearRegression = Arrays.copyOfRange(deltasLinearRegression, 1, deltasLinearRegression.length); var modifiedDeltas = Arrays.copyOfRange(deltas, 1, deltas.length); var deltaSum = Arrays.stream(modifiedDeltas).sum(); var deltaLinearRegressionSum = Arrays.stream(modifiedDeltasLinearRegression).sum(); System.out.println( "delta: " + deltaSum + " linear Regression delta: " + deltaLinearRegressionSum); System.out.println( "max delta: " + Arrays.stream(modifiedDeltas).max().getAsInt() + " max linear Regression delta: " + Arrays.stream(modifiedDeltasLinearRegression).max().getAsDouble()); } } } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/converter/encodings/MltTypeMapTest.java ================================================ package org.maplibre.mlt.converter.encodings; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.List; import java.util.Set; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.maplibre.mlt.metadata.tileset.MltMetadata; /// Test encoding and decoding of column types public class MltTypeMapTest { /// Test that all valid type codes can be decoded and re-encoded to the same value @Test public void roundTrips() { final var valid = Set.of( 0, 1, 2, 3, 4, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30); for (var i = 0; i < 100; ++i) { final MltMetadata.FieldType[] types = new MltMetadata.FieldType[] {null}; if (valid.contains(i)) { final var typeCode = i; Assertions.assertDoesNotThrow( () -> types[0] = MltTypeMap.Tag0x01.decodeColumnType(typeCode)); } else { final var typeCode = i; Assertions.assertThrows( IllegalStateException.class, () -> { types[0] = MltTypeMap.Tag0x01.decodeColumnType(typeCode); }); continue; } var column = types[0]; // STRUCT must have children before being re-encoded if (MltTypeMap.Tag0x01.columnTypeHasChildren(i)) { Assertions.assertNotNull(column.complexType()); Assertions.assertNotNull(column.complexType().children()); column = MltMetadata.structFieldType( List.of( new MltMetadata.Field( MltMetadata.scalarFieldType(MltMetadata.ScalarType.STRING, true)))); } final boolean complex = column.complexType() != null; final boolean logical = (complex && column.complexType().logicalType() != null) || (!complex && column.scalarType().logicalType() != null); final var typeCode = MltTypeMap.Tag0x01.encodeColumnType( (!complex && !logical) ? column.scalarType().physicalType() : null, (!complex && logical) ? column.scalarType().logicalType() : null, (complex && !logical) ? column.complexType().physicalType() : null, (complex && logical) ? column.complexType().logicalType() : null, column.isNullable(), complex && !column.complexType().children().isEmpty(), !complex && column.scalarType().hasLongId()) .or(Assertions::fail); assertEquals(typeCode.get(), i); } } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/converter/encodings/VarintTest.java ================================================ package org.maplibre.mlt.converter.encodings; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.stream.IntStream; import java.util.stream.LongStream; import me.lemire.integercompression.IntWrapper; import org.junit.jupiter.api.Test; import org.locationtech.jts.util.Assert; import org.maplibre.mlt.decoder.DecodingUtils; public class VarintTest { private static final int[] intTestValues = { 0, 1, 0x7F, 0x80, 0xFE, 0xFF, 0x100, 0xFFF, 0x1000, 0x4000, 0xFFFF, 0x10000, 0x40000, 0xFFFFF, 0x100000, 0x1FFFFF, 0x400000, 0xFFFFFFF, 0x10000000, 0x40000000, 0xFFFFFFFF, Integer.MAX_VALUE }; private static final long[] longTestValues = { 0x100000000L, 0x400000000L, 0xFFFFFFFFFL, 0x1000000000000000L, 0x4000000000000000L, Long.MAX_VALUE }; @Test public void testVarIntEncoding() throws IOException { for (int value : Arrays.stream(intTestValues).flatMap(x -> IntStream.of(x, -x)).toArray()) { final var encoded = EncodingUtils.putVarInt(value, ByteBuffer.wrap(new byte[EncodingUtils.MAX_VARINT_SIZE])) .flip(); Assert.equals(EncodingUtils.getVarIntSize(value), encoded.limit()); final var decoded1 = DecodingUtils.decodeVarints(encoded.array(), new IntWrapper(0), 1)[0]; Assert.equals(value, decoded1); final var decoded2 = DecodingUtils.decodeVarint(new ByteArrayInputStream(encoded.array())); Assert.equals(value, decoded2); final var decoded3 = DecodingUtils.decodeVarintWithLength(new ByteArrayInputStream(encoded.array())); Assert.equals(value, decoded3.getLeft()); Assert.equals(encoded.limit(), decoded3.getRight()); } } @Test public void testVarLongEncoding() throws IOException { final var ints = Arrays.stream(intTestValues).asLongStream().flatMap(x -> LongStream.of(x, -x)); final var longs = Arrays.stream(longTestValues).flatMap(x -> LongStream.of(x, -x)); for (long value : LongStream.concat(ints, longs).toArray()) { final var encoded = EncodingUtils.putVarInt(value, ByteBuffer.wrap(new byte[EncodingUtils.MAX_VARLONG_SIZE])) .flip(); Assert.equals(EncodingUtils.getVarLongSize(value), encoded.limit()); final var decoded = DecodingUtils.decodeLongVarint(encoded.array(), new IntWrapper(0)); Assert.equals(value, decoded); } } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/converter/encodings/fsst/FsstTest.java ================================================ package org.maplibre.mlt.converter.encodings.fsst; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; class FsstTest { private static final Fsst JAVA = new FsstJava(); private static final Fsst JNI = new FsstJni(); private static final FsstDebug DEBUG = new FsstDebug(); @AfterAll static void printStats() { System.err.print(FsstDebug.printStatsOnce()); } @Test @Disabled void decode_simpleString_ValidEncodedAndDecoded() { test("AAAAAAABBBAAACCdddddEEEEEEfffEEEEAAAAAddddCC"); } @Test @Disabled void decodeLongRepeated() { test("AAAAAAABBBAAACCdddddEEEEEEfffEEEEAAAAAddddCC".repeat(100)); } @Test @Disabled void empty() { test(""); } @Test @Disabled void repeatedStrings() { for (int i = 1; i < 1000; i++) { test("a".repeat(i)); } } @Test @Disabled void allBytes() { byte[] toEncode = new byte[1000]; for (int i = 0; i < toEncode.length; i++) { toEncode[i] = (byte) i; } test(toEncode); } static List tiles() throws IOException { try (var stream = Files.walk(Path.of("..", "test"))) { return stream .filter(file -> Files.isRegularFile(file) && file.toString().endsWith(".mvt")) .toList(); } } @ParameterizedTest @MethodSource("tiles") @Disabled void fsstEncodeTile(Path path) throws IOException { // stress-test FSST encoding by using it to encode raw tiles // ideally this would encode just the dictionaries, but it's close enough for now test(Files.readAllBytes(path)); } private static void test(String input) { test(input.getBytes(StandardCharsets.UTF_8)); } private static void assertSymbolSortOrder(SymbolTable table) { int last = 2; boolean ones = false; for (int len : table.symbolLengths()) { if (len == 1) { ones = true; } if (ones) { assertEquals( 1, len, () -> "Expected symbols sorted by length with single-byte symbols last, got: " + Arrays.toString(table.symbolLengths())); } else { assertTrue( len >= last, () -> "Expected symbols sorted by length with single-byte symbols last, got: " + Arrays.toString(table.symbolLengths())); last = len; } } } @SuppressWarnings("deprecation") private static void test(byte[] input) { var encodedJava = JAVA.encode(input); var encodedJni = JNI.encode(input); int maxAllowed = Math.max((int) (encodedJni.weight() * 1.02), encodedJni.weight() + 2); assertTrue( encodedJava.weight() <= maxAllowed, () -> """ Input: byte[%d] Encoded java >5%% larger than JNI Java: %s JNI: %s """ .formatted(input.length, encodedJava, encodedJni)); assertSymbolSortOrder(encodedJni); assertSymbolSortOrder(encodedJava); assertArrayEquals(input, JAVA.decode(encodedJava)); assertArrayEquals(input, JAVA.decode(encodedJni)); assertArrayEquals(input, JNI.decode(encodedJava)); assertArrayEquals(input, JNI.decode(encodedJni)); assertArrayEquals( input, JNI.decode( encodedJava.symbols(), encodedJava.symbolLengths(), encodedJava.compressedData())); DEBUG.encode(input); } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/converter/encodings/fsst/SymbolTest.java ================================================ package org.maplibre.mlt.converter.encodings.fsst; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.nio.ByteBuffer; import java.util.Arrays; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; class SymbolTest { Symbol s1 = Symbol.of(1); Symbol s2 = Symbol.of(2); Symbol s3 = Symbol.of(3); Symbol s12 = Symbol.concat(s1, s2); Symbol s12_3 = Symbol.concat(Symbol.concat(s1, s2), s3); Symbol s1_23 = Symbol.concat(s1, Symbol.concat(s2, s3)); Symbol s12121212 = Symbol.concat(Symbol.concat(s12, s12), Symbol.concat(s12, s12)); @Test void testBytes() { assertEquals(1, s1.length()); assertEquals(1, s2.length()); assertSymbol(s1, 1); assertSymbol(s2, 2); assertEquals(2, s12.length()); assertSymbol(s12, 1, 2); assertEquals(3, s12_3.length()); assertEquals(3, s1_23.length()); assertEquals(s12_3.hashCode(), s1_23.hashCode()); assertSymbol(s12_3, 1, 2, 3); assertSymbol(s1_23, 1, 2, 3); assertEquals(8, s12121212.length()); assertSymbol(s12121212, 1, 2, 1, 2, 1, 2, 1, 2); } @Test void testMatch() { assertTrue(s1.match(ByteBuffer.wrap(new byte[] {1}), 0, 0)); assertFalse(s2.match(ByteBuffer.wrap(new byte[] {1}), 0, 0)); assertTrue(s2.match(ByteBuffer.wrap(new byte[] {1, 2}), 1, 0)); assertFalse(s1.match(ByteBuffer.wrap(new byte[] {1, 2}), 1, 0)); } @ParameterizedTest @ValueSource(bytes = {1, (byte) 255}) void testMatch(byte first) { var matcher = Symbol.concat(Symbol.of(first), Symbol.of(2)); byte[] bytesMatching = new byte[] {first, 2, 3, 4, 5, 6, 7, 8, 9, 10}; byte[] bytesNotMatching = new byte[] {first, 3, 3, 4, 5, 6, 7, 8, 9, 10}; for (int i = 2; i < 10; i++) { ByteBuffer bufMatch = ByteBuffer.wrap(bytesMatching, 0, i); ByteBuffer bufNoMatch = ByteBuffer.wrap(bytesNotMatching, 0, i); assertTrue(matcher.match(bufMatch, 0, 0), "expected match " + i); assertFalse(matcher.match(bufNoMatch, 0, 0), "expected no match " + i); assertFalse(matcher.match(bufMatch, 1, 0), "expected match with offset " + i); } } @Test void testCompare() { assertEquals(-1, s1.compareTo(s2)); assertEquals(1, s2.compareTo(s1)); assertEquals(0, s1.compareTo(Symbol.of(1))); assertEquals(-1, s12.compareTo(Symbol.concat(s1, s3))); // longer symbol must compare first assertEquals(-1, s12_3.compareTo(s12)); } private static void assertSymbol(Symbol s, int... ints) { assertEquals(ints.length, s.length()); byte[] bytes = new byte[ints.length]; for (int i = 0; i < ints.length; i++) { bytes[i] = (byte) ints[i]; } assertEquals(ints[0], s.first()); assertEquals(Arrays.hashCode(bytes), s.hashCode()); assertArrayEquals( bytes, s.bytes(), "Expected: %s Actual: %s".formatted(Arrays.toString(bytes), Arrays.toString(s.bytes()))); } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/converter/geometry/HilbertCurveTest.java ================================================ package org.maplibre.mlt.converter.geometry; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; public class HilbertCurveTest { @Test public void level1_hasExpectedStandardOrder() { final var curve = new HilbertCurve(0, 1); assertArrayEquals(new int[] {0, 0}, curve.decode(0)); assertArrayEquals(new int[] {0, 1}, curve.decode(1)); assertArrayEquals(new int[] {1, 1}, curve.decode(2)); assertArrayEquals(new int[] {1, 0}, curve.decode(3)); } @ParameterizedTest(name = "level={0}") @ValueSource(ints = {1, 2, 5, 10, 16}) public void encodeDecode_roundTripsForRepresentativeLevels(int level) { assertRoundTripForLevel(level); } @Test public void consecutiveIndices_areAxisAdjacentAtLevel6() { final int level = 6; final int maxCoordinate = (1 << level) - 1; final var curve = new HilbertCurve(0, maxCoordinate); final long maxIndexExclusive = 1L << (2L * level); final long step = Math.max(1L, maxIndexExclusive / 4096L); for (long index = 0; index < maxIndexExclusive - 1; index += step) { final int currentIndex = (int) index; final int nextIndex = (int) (index + 1); final var a = curve.decode(currentIndex); final var b = curve.decode(nextIndex); final int manhattan = Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]); assertEquals(1, manhattan, "Hilbert curve should move one grid step per index increment"); } } @Test public void throwsWhenLevelIsAboveMaximumAllowed() { assertThrows(IllegalArgumentException.class, () -> assertRoundTripForLevel(17)); } private static void assertRoundTripForLevel(int level) { final int maxCoordinate = (1 << level) - 1; final var curve = new HilbertCurve(0, maxCoordinate); assertRoundTrip(curve, 0, 0); assertRoundTrip(curve, maxCoordinate, 0); assertRoundTrip(curve, 0, maxCoordinate); assertRoundTrip(curve, maxCoordinate, maxCoordinate); assertRoundTrip(curve, maxCoordinate / 2, maxCoordinate / 2); final int step = Math.max(1, maxCoordinate / 7); for (int y = 0; y <= maxCoordinate; y += step) { for (int x = 0; x <= maxCoordinate; x += step) { assertRoundTrip(curve, x, y); } } } private static void assertRoundTrip(HilbertCurve curve, int x, int y) { final int index = curve.encode(new Vertex(x, y)); final int[] decoded = curve.decode(index); assertArrayEquals( new int[] {x, y}, decoded, "Coordinate should round-trip through encode/decode"); } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/converter/geometry/SpaceFillingCurveTest.java ================================================ package org.maplibre.mlt.converter.geometry; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; public class SpaceFillingCurveTest { @Test @Disabled public void numBits_positiveBounds() { SpaceFillingCurve sfc = new TestSpaceFillingCurve(2, 16); assertEquals(4, sfc.numBits()); } @Test public void numBits_zeroAndPositiveBounds() { SpaceFillingCurve sfc = new TestSpaceFillingCurve(0, 16); assertEquals(5, sfc.numBits()); } @Test public void numBits_positiveAndNegativeBounds() { SpaceFillingCurve sfc = new TestSpaceFillingCurve(-16, 16); assertEquals(6, sfc.numBits()); } private static class TestSpaceFillingCurve extends SpaceFillingCurve { public TestSpaceFillingCurve(int minVertexValue, int maxVertexValue) { super(minVertexValue, maxVertexValue); } @Override public int encode(Vertex vertex) { return 0; } @Override public int[] decode(int mortonCode) { return new int[0]; } } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/converter/geometry/ZOrderCurveTest.java ================================================ package org.maplibre.mlt.converter.geometry; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; public class ZOrderCurveTest { @Test public void level1_hasExpectedStandardOrder() { final var curve = new ZOrderCurve(0, 1); assertArrayEquals(new int[] {0, 0}, curve.decode(0)); assertArrayEquals(new int[] {1, 0}, curve.decode(1)); assertArrayEquals(new int[] {0, 1}, curve.decode(2)); assertArrayEquals(new int[] {1, 1}, curve.decode(3)); } @ParameterizedTest(name = "level={0}") @ValueSource(ints = {1, 2, 5, 10, 16}) public void encodeDecode_roundTripsForRepresentativeLevels(int level) { assertRoundTripForLevel(level); } @ParameterizedTest(name = "level={0}") @ValueSource(ints = {1, 2, 5, 10, 16}) public void staticDecode_matchesInstanceDecodeForRepresentativeLevels(int level) { assertStaticDecodeMatchesInstanceDecode(level); } @Test public void throwsWhenLevelIsAboveMaximumAllowed() { assertThrows(IllegalArgumentException.class, () -> assertRoundTripForLevel(17)); } private static void assertRoundTripForLevel(int level) { final int maxCoordinate = (1 << level) - 1; final var curve = new ZOrderCurve(0, maxCoordinate); assertRoundTrip(curve, 0, 0); assertRoundTrip(curve, maxCoordinate, 0); assertRoundTrip(curve, 0, maxCoordinate); assertRoundTrip(curve, maxCoordinate, maxCoordinate); assertRoundTrip(curve, maxCoordinate / 2, maxCoordinate / 2); final int step = Math.max(1, maxCoordinate / 7); for (int y = 0; y <= maxCoordinate; y += step) { for (int x = 0; x <= maxCoordinate; x += step) { assertRoundTrip(curve, x, y); } } } private static void assertStaticDecodeMatchesInstanceDecode(int level) { final int maxCoordinate = (1 << level) - 1; final var curve = new ZOrderCurve(0, maxCoordinate); final long maxIndexExclusive = 1L << (2L * level); final long step = Math.max(1L, maxIndexExclusive / 32L); for (long index = 0; index < maxIndexExclusive; index += step) { final int sampledIndex = (int) index; assertArrayEquals( curve.decode(sampledIndex), ZOrderCurve.decode(sampledIndex, curve.numBits(), curve.coordinateShift()), "Static decode should match instance decode"); } } private static void assertRoundTrip(ZOrderCurve curve, int x, int y) { final int index = curve.encode(new Vertex(x, y)); final int[] decoded = curve.decode(index); assertArrayEquals( new int[] {x, y}, decoded, "Coordinate should round-trip through encode/decode"); } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/converter/tessellation/TessellationUtilsTest.java ================================================ package org.maplibre.mlt.converter.tessellation; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.ArrayList; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.LinearRing; import org.locationtech.jts.geom.Polygon; public class TessellationUtilsTest { private TessellationUtilsTest() {} @Test @Disabled public void tessellateMultiPolygon_PolygonsWithoutHoles() { var geometryFactory = new GeometryFactory(); var shell1 = new Coordinate[] { new Coordinate(0, 0), new Coordinate(10, 0), new Coordinate(10, 10), new Coordinate(0, 10), new Coordinate(0, 0) }; var polygon1 = geometryFactory.createPolygon(shell1); var shell2 = new Coordinate[] { new Coordinate(20, 20), new Coordinate(40, 20), new Coordinate(40, 40), new Coordinate(20, 40), new Coordinate(20, 20) }; var polygon2 = geometryFactory.createPolygon(shell2); var multiPolygon = geometryFactory.createMultiPolygon(new Polygon[] {polygon1, polygon2}); var tessellatedPolygon = TessellationUtils.tessellateMultiPolygon(multiPolygon, null); var expectedIndexBuffer = Stream.of(3, 0, 1, 1, 2, 3, 7, 4, 5, 5, 6, 7) .collect(Collectors.toCollection(ArrayList::new)); assertEquals(4, tessellatedPolygon.numTriangles()); assertEquals(expectedIndexBuffer, tessellatedPolygon.indexBuffer()); } @Test public void tessellateMultiPolygon_PolygonsWithHoles() { var geometryFactory = new GeometryFactory(); var shell1 = new Coordinate[] { new Coordinate(0, 0), new Coordinate(10, 0), new Coordinate(10, 10), new Coordinate(0, 10), new Coordinate(0, 0) }; var hole1 = new Coordinate[] { new Coordinate(5, 5), new Coordinate(5, 7), new Coordinate(7, 7), new Coordinate(7, 5), new Coordinate(5, 5) }; /*var hole2 = new Coordinate[] { new Coordinate(8, 8), new Coordinate(8, 9), new Coordinate(9, 9), new Coordinate(9, 8), new Coordinate(8, 8) };*/ var polygon1 = geometryFactory.createPolygon( geometryFactory.createLinearRing(shell1), new LinearRing[] {geometryFactory.createLinearRing(hole1)}); var shell2 = new Coordinate[] { new Coordinate(20, 20), new Coordinate(40, 20), new Coordinate(40, 40), new Coordinate(20, 40), new Coordinate(20, 20) }; var polygon2 = geometryFactory.createPolygon(shell2); var multiPolygon = geometryFactory.createMultiPolygon(new Polygon[] {polygon1, polygon2}); var tessellatedPolygon = TessellationUtils.tessellateMultiPolygon(multiPolygon, null); assertEquals(10, tessellatedPolygon.numTriangles()); } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/data/unsigned/UnsignedTest.java ================================================ package org.maplibre.mlt.data.unsigned; import static org.junit.jupiter.api.Assertions.*; import java.math.BigInteger; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; /** Tests for the {@link Unsigned} interface and its implementations */ class UnsignedTest { @Test void testU8OfValidValues() { final var u8Zero = U8.of(0); assertEquals((byte) 0, u8Zero.byteValue()); assertEquals(0, u8Zero.intValue()); assertEquals(0L, u8Zero.longValue()); final var u8Max = U8.of(255); assertEquals((byte) 255, u8Max.byteValue()); assertEquals(255, u8Max.intValue()); assertEquals(255L, u8Max.longValue()); final var u8Mid = U8.of(128); assertEquals((byte) 128, u8Mid.byteValue()); assertEquals(128, u8Mid.intValue()); assertEquals(128L, u8Mid.longValue()); } @Test void testU8OfNegativeValue() { assertThrows( IllegalArgumentException.class, () -> U8.of(-1), "U8 should reject negative values"); } @Test void testU8OfValueTooLarge() { assertThrows(IllegalArgumentException.class, () -> U8.of(256), "U8 should reject values > 255"); } @ParameterizedTest @ValueSource(ints = {0, 1, 127, 128, 254, 255}) void testU8ValidRange(int value) { final var u8 = U8.of(value); assertEquals(value, u8.intValue(), "U8 should preserve unsigned value as int"); assertEquals((long) value, u8.longValue(), "U8 should preserve unsigned value as long"); } @Test void testU8RecordProperties() { assertEquals(42, (int) U8.of(42).value(), "Record should expose value"); } @Test void testU8ToString() { assertEquals("u8(0)", U8.of(0).toString()); assertEquals("u8(255)", U8.of(255).toString()); assertEquals("u8(42)", U8.of(42).toString()); } @Test void testU8Equality() { final var u8a = U8.of(42); final var u8b = U8.of(42); final var u8c = U8.of(43); assertEquals(u8a, u8b, "U8 records with same value should be equal"); assertNotEquals(u8a, u8c, "U8 records with different values should not be equal"); } @Test void testU8ImplementsUnsigned() { final var u8 = U8.of(100); assertTrue(u8 instanceof Unsigned, "U8 should implement Unsigned"); assertNotNull(u8.byteValue()); assertNotNull(u8.intValue()); assertNotNull(u8.longValue()); } @Test void testU32OfValidValues() { // Test boundary values final var u32Zero = U32.of(0); assertEquals(0, u32Zero.intValue()); assertEquals(0L, u32Zero.longValue()); final var u32Max = U32.of(0xFFFFFFFFL); assertEquals(-1, u32Max.intValue()); // -1 in signed int representation assertEquals(0xFFFFFFFFL, u32Max.longValue()); // Correct unsigned value final var u32Mid = U32.of(0x80000000L); assertEquals(0x80000000L, u32Mid.longValue()); } @Test void testU32OfNegativeValue() { assertThrows( IllegalArgumentException.class, () -> U32.of(-1), "U32 should reject negative values"); } @Test void testU32OfValueTooLarge() { assertThrows( IllegalArgumentException.class, () -> U32.of(0x100000000L), "U32 should reject values > 2^32-1"); } @ParameterizedTest @ValueSource(longs = {0L, 1L, 0x7FFFFFFFL, 0x80000000L, 0xFFFFFFFEL, 0xFFFFFFFFL}) void testU32ValidRange(long value) { assertEquals(value, U32.of(value).longValue(), "U32 should preserve unsigned value as long"); } @Test void testU32RecordProperties() { assertEquals(0x12345678, U32.of(0x12345678L).value(), "Record should expose value"); } @Test void testU32ToString() { assertEquals("u32(0)", U32.of(0).toString()); assertEquals("u32(4294967295)", U32.of(0xFFFFFFFFL).toString()); assertEquals("u32(305419896)", U32.of(0x12345678L).toString()); } @Test void testU32Equality() { final var u32a = U32.of(0x12345678L); final var u32b = U32.of(0x12345678L); final var u32c = U32.of(0x12345679L); assertEquals(u32a, u32b, "U32 records with same value should be equal"); assertNotEquals(u32a, u32c, "U32 records with different values should not be equal"); } @Test void testU32ImplementsUnsigned() { final var u32 = U32.of(1000000L); assertTrue(u32 instanceof Unsigned, "U32 should implement Unsigned"); assertNotNull(u32.intValue()); assertNotNull(u32.longValue()); } @Test void testU32ByteValueReturnsNullWhenOutOfRange() { final var u32InRange = U32.of(100); assertNotNull(u32InRange.byteValue(), "U32 with value in byte range should return non-null"); final var u32OutOfRange = U32.of(256); assertNull(u32OutOfRange.byteValue(), "U32 with value > 255 should return null for byteValue"); } @Test void testU32ByteValueBoundary() { final var u32At127 = U32.of(127); assertNotNull(u32At127.byteValue(), "U32 with value 127 should return non-null"); final var u32At128 = U32.of(128); assertNull(u32At128.byteValue(), "U32 with value 128 should return null (negative as byte)"); } @Test void testU64OfValidValues() { // Test boundary values final var u64Zero = U64.of(BigInteger.ZERO); assertEquals(0L, u64Zero.longValue()); assertEquals(0, u64Zero.intValue()); final var u64Max = U64.of(new BigInteger("18446744073709551615")); // 2^64 - 1 assertEquals(-1L, u64Max.longValue()); // -1 in signed long representation } @Test void testU64OfNegativeValue() { assertThrows( IllegalArgumentException.class, () -> U64.of(BigInteger.valueOf(-1)), "U64 should reject negative values"); } @Test void testU64OfValueTooLarge() { final var tooLarge = new BigInteger("18446744073709551616"); // 2^64 assertThrows( IllegalArgumentException.class, () -> U64.of(tooLarge), "U64 should reject values >= 2^64"); } @ParameterizedTest @ValueSource(longs = {0L, 1L, Long.MAX_VALUE, -1L}) void testU64ValidRange(long value) { final var u64 = U64.of(BigInteger.valueOf(value & 0x7FFFFFFFFFFFFFFFL)); assertNotNull(u64.longValue(), "U64 should accept valid unsigned values"); } @Test void testU64RecordProperties() { final var u64 = U64.of(BigInteger.valueOf(0x123456789ABCDEFL)); assertEquals(0x123456789ABCDEFL, u64.value(), "Record should expose value"); } @Test void testU64ToString() { assertEquals("u64(0)", U64.of(BigInteger.ZERO).toString()); final var u64Max = U64.of(new BigInteger("18446744073709551615")); assertEquals("u64(18446744073709551615)", u64Max.toString()); } @Test void testU64Equality() { final var u64a = U64.of(BigInteger.valueOf(0x123456789ABCDEFL)); final var u64b = U64.of(BigInteger.valueOf(0x123456789ABCDEFL)); final var u64c = U64.of(BigInteger.valueOf(0x123456789ABCDF0L)); assertEquals(u64a, u64b, "U64 records with same value should be equal"); assertNotEquals(u64a, u64c, "U64 records with different values should not be equal"); } @Test void testU64ImplementsUnsigned() { final var u64 = U64.of(BigInteger.valueOf(1000000L)); assertTrue(u64 instanceof Unsigned, "U64 should implement Unsigned"); assertNotNull(u64.longValue()); } @Test void testU64ByteValueReturnsNullWhenOutOfRange() { final var u64InRange = U64.of(BigInteger.valueOf(100)); assertNotNull(u64InRange.byteValue(), "U64 with value in byte range should return non-null"); final var u64OutOfRange = U64.of(BigInteger.valueOf(256)); assertNull(u64OutOfRange.byteValue(), "U64 with value > 255 should return null for byteValue"); } @Test void testU64IntValueReturnsNullWhenOutOfRange() { final var u64InRange = U64.of(BigInteger.valueOf(0x12345678L)); assertNotNull(u64InRange.intValue(), "U64 with value in int range should return non-null"); final var u64OutOfRange = U64.of(BigInteger.valueOf(0x100000000L)); assertNull(u64OutOfRange.intValue(), "U64 with value > 2^31-1 should return null for intValue"); } @Test void testU64ByteAndIntValueBoundaries() { // Test byte boundary - byteValue returns non-null only for 0-127 final var u64At127 = U64.of(BigInteger.valueOf(127)); assertNotNull(u64At127.byteValue(), "U64 with value 127 should return non-null for byteValue"); final var u64At128 = U64.of(BigInteger.valueOf(128)); assertNull( u64At128.byteValue(), "U64 with value 128 should return null for byteValue (negative as byte)"); // Test int boundary final var u64AtMaxInt = U64.of(BigInteger.valueOf(0x7FFFFFFFL)); assertNotNull( u64AtMaxInt.intValue(), "U64 with value 2^31-1 should return non-null for intValue"); final var u64AtMaxIntPlus1 = U64.of(BigInteger.valueOf(0x80000000L)); assertNull( u64AtMaxIntPlus1.intValue(), "U64 with value 2^31 should return null for intValue (negative as int)"); } @Test void testConversionBetweenTypes() { final var u8 = U8.of(42); final var u32 = U32.of(42L); final var u64 = U64.of(BigInteger.valueOf(42)); assertEquals(u8.intValue(), u32.intValue()); assertEquals(u32.longValue(), u64.longValue()); assertEquals(u8.longValue(), u64.longValue()); } @Test void testU8WithHighByteValue() { // Test that U8 properly handles value 255 as unsigned final var u8 = U8.of(255); assertEquals((byte) -1, u8.byteValue()); // Signed byte representation assertEquals(255, u8.intValue()); // Unsigned int value assertEquals(255L, u8.longValue()); // Unsigned long value } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/decoder/ByteRleTest.java ================================================ package org.maplibre.mlt.decoder; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import me.lemire.integercompression.IntWrapper; import org.junit.jupiter.api.Test; import org.maplibre.mlt.converter.encodings.ByteRleEncoder; public class ByteRleTest { @Test public void testEncodeDecodeRun() throws IOException { // Test encoding a run of identical bytes byte[] values = new byte[] {1, 1, 1, 1, 1, 1, 1}; byte[] encoded = ByteRleEncoder.encode(values); assertNotNull(encoded); // Decode ByteRleDecoder decoder = new ByteRleDecoder(encoded, 0, encoded.length); byte[] decoded = new byte[values.length]; for (int i = 0; i < values.length; i++) { decoded[i] = decoder.next(); } assertArrayEquals(values, decoded); } @Test public void testEncodeDecodeLiterals() throws IOException { // Test encoding literal bytes (no runs) byte[] values = new byte[] {1, 2, 3, 4, 5, 6, 7}; byte[] encoded = ByteRleEncoder.encode(values); assertNotNull(encoded); // Decode ByteRleDecoder decoder = new ByteRleDecoder(encoded, 0, encoded.length); byte[] decoded = new byte[values.length]; for (int i = 0; i < values.length; i++) { decoded[i] = decoder.next(); } assertArrayEquals(values, decoded); } @Test public void testEncodeDecodeMixed() throws IOException { // Test encoding a mix of runs and literals byte[] values = new byte[] {1, 1, 1, 2, 3, 4, 5, 5, 5, 5}; byte[] encoded = ByteRleEncoder.encode(values); assertNotNull(encoded); // Decode ByteRleDecoder decoder = new ByteRleDecoder(encoded, 0, encoded.length); byte[] decoded = new byte[values.length]; for (int i = 0; i < values.length; i++) { decoded[i] = decoder.next(); } assertArrayEquals(values, decoded); } @Test public void testEncodeDecodeLongRun() throws IOException { // Test encoding a long run (exceeding minimum repeat size) byte[] values = new byte[150]; for (int i = 0; i < values.length; i++) { values[i] = 42; } byte[] encoded = ByteRleEncoder.encode(values); assertNotNull(encoded); // Should be compressed significantly assertTrue(encoded.length < values.length); // Decode ByteRleDecoder decoder = new ByteRleDecoder(encoded, 0, encoded.length); byte[] decoded = new byte[values.length]; for (int i = 0; i < values.length; i++) { decoded[i] = decoder.next(); } assertArrayEquals(values, decoded); } @Test public void testEncodeSingleByte() throws IOException { byte[] values = new byte[] {7}; byte[] encoded = ByteRleEncoder.encode(values); assertNotNull(encoded); // Decode ByteRleDecoder decoder = new ByteRleDecoder(encoded, 0, encoded.length); assertEquals(7, decoder.next()); } @Test public void testEncodeEmpty() throws IOException { byte[] values = new byte[0]; byte[] encoded = ByteRleEncoder.encode(values); assertNotNull(encoded); assertEquals(0, encoded.length); } @Test public void testEncoderFlush() throws IOException { ByteRleEncoder encoder = new ByteRleEncoder(); encoder.write((byte) 1); encoder.write((byte) 2); encoder.write((byte) 3); byte[] encoded = encoder.toByteArray(); assertNotNull(encoded); ByteRleDecoder decoder = new ByteRleDecoder(encoded, 0, encoded.length); assertEquals(1, decoder.next()); assertEquals(2, decoder.next()); assertEquals(3, decoder.next()); } @Test public void testCompatibilityWithDecodingUtils() throws IOException { // Test that our implementation works with the existing DecodingUtils byte[] values = new byte[] {5, 5, 5, 5, 5}; byte[] encoded = ByteRleEncoder.encode(values); IntWrapper pos = new IntWrapper(0); byte[] decoded = DecodingUtils.decodeByteRle(encoded, values.length, encoded.length, pos); assertArrayEquals(values, decoded); assertEquals(encoded.length, pos.get()); } @Test public void testMinimalRun() throws IOException { // Test the minimum run size (3 bytes) byte[] values = new byte[] {7, 7, 7}; byte[] encoded = ByteRleEncoder.encode(values); assertNotNull(encoded); ByteRleDecoder decoder = new ByteRleDecoder(encoded, 0, encoded.length); assertEquals(7, decoder.next()); assertEquals(7, decoder.next()); assertEquals(7, decoder.next()); } @Test public void testMaxLiteralSize() throws IOException { // Test encoding maximum literal size (128 bytes) byte[] values = new byte[128]; for (int i = 0; i < values.length; i++) { values[i] = (byte) (i % 256); } byte[] encoded = ByteRleEncoder.encode(values); assertNotNull(encoded); ByteRleDecoder decoder = new ByteRleDecoder(encoded, 0, encoded.length); byte[] decoded = new byte[values.length]; for (int i = 0; i < values.length; i++) { decoded[i] = decoder.next(); } assertArrayEquals(values, decoded); } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/decoder/DecodingUtilsTest.java ================================================ package org.maplibre.mlt.decoder; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import java.io.IOException; import me.lemire.integercompression.IntWrapper; import org.junit.jupiter.api.Test; import org.locationtech.jts.util.Assert; import org.maplibre.mlt.converter.encodings.EncodingUtils; import org.maplibre.mlt.decoder.vectorized.VectorizedDecodingUtils; public class DecodingUtilsTest { @Test public void decodeLongVarints() throws IOException { var value = (long) Math.pow(2, 54); var value2 = (long) Math.pow(2, 17); var value3 = (long) Math.pow(2, 57); var encodedValues = EncodingUtils.encodeVarints(new long[] {value, value2, value3}, false, false); final var pos = new IntWrapper(0); final var decodedValue = DecodingUtils.decodeLongVarints(encodedValues, pos, 3); Assert.equals(value, decodedValue[0]); Assert.equals(value2, decodedValue[1]); Assert.equals(value3, decodedValue[2]); Assert.equals(encodedValues.length, pos.get()); } @Test public void decodeZigZag_LongValue() { var value = (long) Math.pow(2, 54); var value2 = (long) -Math.pow(2, 44); var encodedValue = EncodingUtils.encodeZigZag(value); var encodedValue2 = EncodingUtils.encodeZigZag(value2); var decodedValue = DecodingUtils.decodeZigZag(encodedValue); var decodedValue2 = DecodingUtils.decodeZigZag(encodedValue2); Assert.equals(value, decodedValue); Assert.equals(value2, decodedValue2); } @Test void decodeFastPfor() { var values = new int[] {5, 10, 15, 20, 25, 30, 35, 40}; var encoded = EncodingUtils.encodeFastPfor128(values, false, false); var pos = new IntWrapper(0); var decoded = VectorizedDecodingUtils.decodeFastPfor(encoded, values.length, encoded.length, pos); assertArrayEquals(values, decoded.array()); } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/decoder/DoubleDecoderTest.java ================================================ package org.maplibre.mlt.decoder; import java.io.IOException; import java.util.List; import me.lemire.integercompression.IntWrapper; import org.junit.jupiter.api.Test; import org.locationtech.jts.util.Assert; import org.maplibre.mlt.converter.encodings.DoubleEncoder; import org.maplibre.mlt.converter.encodings.FloatEncoder; import org.maplibre.mlt.metadata.stream.StreamMetadata; import org.maplibre.mlt.util.ByteArrayUtil; public class DoubleDecoderTest { @Test public void decodeDoubleStream_DoubleEncodedValues_ReturnsExactValues() throws IOException { final var encoded = ByteArrayUtil.concat(DoubleEncoder.encodeDoubleStream(f64Values)); final var offset = new IntWrapper(0); final var streamMetadata = StreamMetadata.decode(encoded, offset); final var decoded = DoubleDecoder.decodeDoubleStream(encoded, offset, streamMetadata); Assert.equals(f64Values, decoded); } @Test public void decodeDoubleStream_FloatEncodedValues_ReturnsConvertedDoubleValues() throws IOException { final var f32Values = f64Values.stream().map(Double::floatValue).toList(); final var encoded = ByteArrayUtil.concat(FloatEncoder.encodeFloatStream(f32Values)); final var offset = new IntWrapper(0); final var streamMetadata = StreamMetadata.decode(encoded, offset); final var decoded = DoubleDecoder.decodeDoubleStream(encoded, offset, streamMetadata); Assert.equals(f32Values.stream().map(Double::valueOf).toList(), decoded); } @Test public void decodeDoubleStream_EmptyStream_ReturnsEmptyList() throws IOException { final var values = List.of(); final var encoded = ByteArrayUtil.concat(DoubleEncoder.encodeDoubleStream(values)); final var offset = new IntWrapper(0); final var streamMetadata = StreamMetadata.decode(encoded, offset); final var decoded = DoubleDecoder.decodeDoubleStream(encoded, offset, streamMetadata); Assert.equals(values, decoded); } final List f64Values = List.of( 1.25, -3.5, 6.02214076e23, -0.0, 123456.789, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN); } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/decoder/IntegerDecoderTest.java ================================================ package org.maplibre.mlt.decoder; import java.io.IOException; import java.util.ArrayList; import java.util.List; import me.lemire.integercompression.IntWrapper; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.locationtech.jts.util.Assert; import org.maplibre.mlt.converter.CollectionUtils; import org.maplibre.mlt.converter.encodings.EncodingUtils; import org.maplibre.mlt.converter.encodings.IntegerEncoder; import org.maplibre.mlt.metadata.stream.LogicalLevelTechnique; import org.maplibre.mlt.metadata.stream.LogicalStreamType; import org.maplibre.mlt.metadata.stream.PhysicalLevelTechnique; import org.maplibre.mlt.metadata.stream.PhysicalStreamType; import org.maplibre.mlt.metadata.stream.StreamMetadata; import org.maplibre.mlt.util.ByteArrayUtil; public class IntegerDecoderTest { @Test public void encode_Int_Limits() throws IOException { for (int v : new int[] {Integer.MIN_VALUE, -1, 0, 1, Integer.MAX_VALUE}) { final var encoded = EncodingUtils.encodeVarint(v, false); final var decoded = DecodingUtils.decodeVarints(encoded, new IntWrapper(0), 1)[0]; Assert.equals(decoded, v); } } @Test public void encode_Int_Limits_ZigZag() throws IOException { for (int v : new int[] {Integer.MIN_VALUE, -1, 0, 1, Integer.MAX_VALUE}) { final var encoded = EncodingUtils.encodeVarint(v, true); final var zigzag = DecodingUtils.decodeVarints(encoded, new IntWrapper(0), 1)[0]; final var decoded = DecodingUtils.decodeZigZag(zigzag); Assert.equals(decoded, v); } } private static ArrayList encodeIntStream( List values, PhysicalLevelTechnique physicalLevelTechnique, @SuppressWarnings("SameParameterValue") boolean isSigned, @SuppressWarnings("SameParameterValue") PhysicalStreamType streamType, @SuppressWarnings("SameParameterValue") LogicalStreamType logicalStreamType) throws IOException { return IntegerEncoder.encodeIntStream( values, physicalLevelTechnique, isSigned, streamType, logicalStreamType); } @Test public void decodeIntStream_SignedIntegerValues_PlainFastPforEncode() throws IOException { var values = List.of(1, 2, 7, 3, -4, 5, 1, -8); var encodedStream = ByteArrayUtil.concat( encodeIntStream( values, PhysicalLevelTechnique.FAST_PFOR, true, PhysicalStreamType.DATA, null)); var offset = new IntWrapper(0); var streamMetadata = StreamMetadata.decode(encodedStream, offset); var decodedValues = IntegerDecoder.decodeIntStream(encodedStream, offset, streamMetadata, true); Assert.equals(values, decodedValues); Assert.equals(LogicalLevelTechnique.NONE, streamMetadata.logicalLevelTechnique1()); } @Test public void decodeIntStream_SignedIntegerValues_PlainVarintEncode() throws IOException { var values = List.of(1, 2, 7, 3, -4, 5, 1, -8); var encodedStream = ByteArrayUtil.concat( encodeIntStream( values, PhysicalLevelTechnique.VARINT, true, PhysicalStreamType.DATA, null)); var offset = new IntWrapper(0); var streamMetadata = StreamMetadata.decode(encodedStream, offset); var decodedValues = IntegerDecoder.decodeIntStream(encodedStream, offset, streamMetadata, true); Assert.equals(values, decodedValues); Assert.equals(LogicalLevelTechnique.NONE, streamMetadata.logicalLevelTechnique1()); } @Test @Disabled public void decodeIntStream_SignedIntegerValues_FastPforDeltaRleEncode() throws IOException { var values = List.of(-1, -2, -3, -4, -5, -6, -7, 8); var encodedStream = ByteArrayUtil.concat( encodeIntStream( values, PhysicalLevelTechnique.FAST_PFOR, true, PhysicalStreamType.DATA, null)); var offset = new IntWrapper(0); var streamMetadata = StreamMetadata.decode(encodedStream, offset); var decodedValues = IntegerDecoder.decodeIntStream(encodedStream, offset, streamMetadata, true); Assert.equals(values, decodedValues); } @Test @Disabled public void decodeIntStream_SignedIntegerValues_VarintDeltaRleEncode() throws IOException { var values = List.of(-1, -2, -3, -4, -5, -6, -7, 8); var encodedStream = ByteArrayUtil.concat( encodeIntStream( values, PhysicalLevelTechnique.VARINT, true, PhysicalStreamType.DATA, null)); var offset = new IntWrapper(0); var streamMetadata = StreamMetadata.decode(encodedStream, offset); var decodedValues = IntegerDecoder.decodeIntStream(encodedStream, offset, streamMetadata, true); Assert.equals(values, decodedValues); } @Test @Disabled public void decodeIntStream_SignedIntegerValues_FastPforRleEncode() throws IOException { var values = List.of(-1, -1, -1, -1, -1, -1, -2, -2); var encodedStream = ByteArrayUtil.concat( encodeIntStream( values, PhysicalLevelTechnique.FAST_PFOR, true, PhysicalStreamType.DATA, null)); var offset = new IntWrapper(0); var streamMetadata = StreamMetadata.decode(encodedStream, offset); var decodedValues = IntegerDecoder.decodeIntStream(encodedStream, offset, streamMetadata, true); Assert.equals(values, decodedValues); Assert.equals(LogicalLevelTechnique.RLE, streamMetadata.logicalLevelTechnique1()); } @Test @Disabled public void decodeIntStream_SignedIntegerValues_VarintRleEncode() throws IOException { var values = List.of(-1, -1, -1, -1, -1, -1, -2, -2); var encodedStream = ByteArrayUtil.concat( encodeIntStream( values, PhysicalLevelTechnique.VARINT, true, PhysicalStreamType.DATA, null)); var offset = new IntWrapper(0); var streamMetadata = StreamMetadata.decode(encodedStream, offset); var decodedValues = IntegerDecoder.decodeIntStream(encodedStream, offset, streamMetadata, true); Assert.equals(values, decodedValues); Assert.equals(LogicalLevelTechnique.RLE, streamMetadata.logicalLevelTechnique1()); } @Test @Disabled public void decodeIntStream_UnsignedIntegerValues_VarintRleEncode() throws IOException { var values = List.of(1, 1, 1, 1, 1, 1, 2, 2); var encodedStream = ByteArrayUtil.concat( encodeIntStream( values, PhysicalLevelTechnique.VARINT, true, PhysicalStreamType.DATA, null)); var offset = new IntWrapper(0); var streamMetadata = StreamMetadata.decode(encodedStream, offset); var decodedValues = IntegerDecoder.decodeIntStream(encodedStream, offset, streamMetadata, true); Assert.equals(values, decodedValues); Assert.equals(LogicalLevelTechnique.RLE, streamMetadata.logicalLevelTechnique1()); } private static ArrayList encodeLongStream( List values, @SuppressWarnings("SameParameterValue") boolean isSigned, @SuppressWarnings("SameParameterValue") PhysicalStreamType streamType, @SuppressWarnings("SameParameterValue") LogicalStreamType logicalStreamType) throws IOException { return IntegerEncoder.encodeLongStream( CollectionUtils.unboxLongs(values), isSigned, streamType, logicalStreamType); } @Test @Disabled public void decodeLongStream_SignedIntegerValues_PlainEncode() throws IOException { final var values = List.of(1L, 2L, 7L, 3L, -4L, 5L, 1L, -8L); var encodedStream = ByteArrayUtil.concat(encodeLongStream(values, true, PhysicalStreamType.DATA, null)); var offset = new IntWrapper(0); var streamMetadata = StreamMetadata.decode(encodedStream, offset); var decodedValues = IntegerDecoder.decodeLongStream(encodedStream, offset, streamMetadata, true); Assert.equals(values, decodedValues); Assert.equals(LogicalLevelTechnique.NONE, streamMetadata.logicalLevelTechnique1()); } @Test @Disabled public void decodeLongStream_SignedIntegerValues_DeltaRleEncode() throws IOException { final var values = List.of(-1L, -2L, -3L, -4L, -5L, -6L, -7L, 8L); var encodedStream = ByteArrayUtil.concat(encodeLongStream(values, true, PhysicalStreamType.DATA, null)); var offset = new IntWrapper(0); var streamMetadata = StreamMetadata.decode(encodedStream, offset); var decodedValues = IntegerDecoder.decodeLongStream(encodedStream, offset, streamMetadata, true); Assert.equals(values, decodedValues); } @Test @Disabled public void decodeLongStream_SignedIntegerValues_RleEncode() throws IOException { final var values = List.of(-1L, -1L, -1L, -1L, -1L, -1L, -2L, -2L); var encodedStream = ByteArrayUtil.concat(encodeLongStream(values, true, PhysicalStreamType.DATA, null)); var offset = new IntWrapper(0); var streamMetadata = StreamMetadata.decode(encodedStream, offset); var decodedValues = IntegerDecoder.decodeLongStream(encodedStream, offset, streamMetadata, true); Assert.equals(values, decodedValues); Assert.equals(LogicalLevelTechnique.RLE, streamMetadata.logicalLevelTechnique1()); } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/decoder/MltDecoderTest.java ================================================ package org.maplibre.mlt.decoder; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Paths; import java.util.List; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.lang3.tuple.Triple; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.maplibre.mlt.TestSettings; import org.maplibre.mlt.TestUtils; import org.maplibre.mlt.compare.CompareHelper; import org.maplibre.mlt.converter.ColumnMapping; import org.maplibre.mlt.converter.ColumnMappingConfig; import org.maplibre.mlt.converter.ConversionConfig; import org.maplibre.mlt.converter.FeatureTableOptimizations; import org.maplibre.mlt.converter.MltConverter; import org.maplibre.mlt.converter.mvt.MvtUtils; import org.maplibre.mlt.data.MapboxVectorTile; import org.maplibre.mlt.metadata.tileset.MltMetadata; @FunctionalInterface interface TriConsumer { void apply(A a, B b, C c) throws IOException; } public class MltDecoderTest { private static Stream> bingMapsTileIdProvider() { return Stream.of( Triple.of(4, 8, 5), Triple.of(5, 16, 11), Triple.of(6, 32, 22), Triple.of(7, 65, 42)); } private static Stream> omtTileIdProvider() { return Stream.of( Triple.of(0, 0, 0), Triple.of(1, 1, 1), // Triple.of(2, 2, 2), // TODO: fix -> 2_2_2 Triple.of(3, 4, 5), Triple.of(4, 8, 10), Triple.of(5, 16, 21), Triple.of(6, 32, 41), Triple.of(7, 66, 84), Triple.of(8, 134, 171), Triple.of(9, 265, 341), Triple.of(10, 532, 682), Triple.of(11, 1064, 1367), Triple.of(12, 2132, 2734), Triple.of(13, 4265, 5467), Triple.of(14, 8298, 10748)); } /* Decode tiles in an in-memory format optimized for sequential access */ @DisplayName("Decode scalar unsorted OpenMapTiles schema based vector tiles") @ParameterizedTest @MethodSource("omtTileIdProvider") public void decodeMlTile_UnsortedOMT(Triple tileId) throws IOException, URISyntaxException { final var id = String.format("%s_%s_%s", tileId.getLeft(), tileId.getMiddle(), tileId.getRight()); final var useFastPFOR = (tileId.getLeft() & 1) != 0; final var useFSST = (tileId.getMiddle() & 1) != 0; // TODO: Why doesn't tessellation work for these final var tessellate = false; // (tileId.getRight() & 1) != 0; testTileSequential(id, TestSettings.OMT_MVT_PATH, useFastPFOR, useFSST, tessellate); } private void testTileSequential( String tileId, String tileDirectory, boolean useFastPFOR, boolean useFSST, boolean tessellate) throws IOException, URISyntaxException { testTile( tileId, tileDirectory, (mlTile, tileMetadata, mvTile) -> { final var decodedTile = MltDecoder.decodeMlTile(mlTile); Assertions.assertNotNull(decodedTile); final var compareResult = CompareHelper.compareTiles(decodedTile, mvTile, CompareHelper.CompareMode.All); Assertions.assertFalse(compareResult.isPresent()); }, TestUtils.Optimization.NONE, List.of(), true, true, tessellate); } private void testTile( String tileId, String tileDirectory, TriConsumer decodeAndCompare, @SuppressWarnings("SameParameterValue") TestUtils.Optimization optimization, List reassignableLayers, @SuppressWarnings("SameParameterValue") boolean useFastPFOR, @SuppressWarnings("SameParameterValue") boolean useFSST, @SuppressWarnings("SameParameterValue") boolean tessellate) throws IOException, URISyntaxException { var mvtFilePath = Paths.get(tileDirectory, tileId + ".mvt"); var mvTile = MvtUtils.decodeMvt(mvtFilePath); final var columnMapping = new ColumnMapping("name", ":", true); final var columnMappings = ColumnMappingConfig.of(Pattern.compile(".*"), List.of(columnMapping)); final var isIdPresent = true; final var tileMetadata = MltConverter.createTilesetMetadata(mvTile, columnMappings, isIdPresent); var allowSorting = optimization == TestUtils.Optimization.SORTED; var featureTableOptimization = new FeatureTableOptimizations(allowSorting, false, List.of(columnMapping)); var optimizations = TestSettings.OPTIMIZED_MVT_LAYERS.stream() .collect(Collectors.toMap(l -> l, l -> featureTableOptimization)); /* Only regenerate the ids for specific layers when the column is not sorted for comparison reasons */ if (optimization == TestUtils.Optimization.IDS_REASSIGNED) { for (var reassignableLayer : reassignableLayers) { optimizations.put( reassignableLayer, new FeatureTableOptimizations(false, true, List.of(columnMapping))); } } final var config = ConversionConfig.builder() .includeIds(true) .useFastPFOR(useFastPFOR) .useFSST(useFSST) .optimizations(optimizations) .preTessellatePolygons(tessellate) .build(); final var mlTile = MltConverter.encode(mvTile, tileMetadata, config, null); Assertions.assertNotNull(mlTile); decodeAndCompare.apply(mlTile, tileMetadata, mvTile); } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/decoder/MltDecoderTest2.java ================================================ package org.maplibre.mlt.decoder; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.nio.file.Paths; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.lang3.NotImplementedException; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.maplibre.mlt.TestSettings; import org.maplibre.mlt.TestUtils; import org.maplibre.mlt.converter.ColumnMappingConfig; import org.maplibre.mlt.converter.ConversionConfig; import org.maplibre.mlt.converter.FeatureTableOptimizations; import org.maplibre.mlt.converter.MltConverter; import org.maplibre.mlt.converter.mvt.MvtUtils; enum DecoderType { BOTH, SEQUENTIAL, VECTORIZED } enum EncodingType { BOTH, NONADVANCED, ADVANCED } record DecodingResult(int numErrors, int numErrorsAdvanced) {} public class MltDecoderTest2 { /* Bing Maps tests --------------------------------------------------------- */ // TODO: after https://github.com/maplibre/maplibre-tile-spec/issues/186 is fixed // remove this test and start testing all the Bing tiles with sorting enabled @Test @Disabled public void decodeBingTilesSortedFail() throws IOException { var tileId = "4-8-5"; var result = testTile(tileId, TestSettings.BING_MVT_PATH, DecoderType.BOTH, EncodingType.ADVANCED, true); assertEquals( 1148, result.numErrorsAdvanced(), "Error for " + tileId + "/advanced: " + result.numErrorsAdvanced()); } private static Stream bingProvider() { return Stream.of( "4-8-5", "4-9-5", "4-12-6", "4-13-6", "5-16-11", "5-17-11", "5-17-10", "6-32-22", "6-33-22", "6-32-23", "6-32-21", "7-65-42", "7-66-42", "7-66-43", "7-66-44"); } @DisplayName("Decode Bing Tiles") @ParameterizedTest @MethodSource("bingProvider") @Disabled public void decodeBingTiles(String tileId) throws IOException { var result = testTile(tileId, TestSettings.BING_MVT_PATH, DecoderType.BOTH, EncodingType.BOTH, false); assertEquals( 0, result.numErrors(), "Error for " + tileId + "/non-advanced: " + result.numErrors()); assertEquals( 0, result.numErrorsAdvanced(), "Error for " + tileId + "/advanced: " + result.numErrorsAdvanced()); } // TODO: // once https://github.com/maplibre/maplibre-tile-spec/issues/182 is fixed // add the "5-16-9" tile to the bingProvider // and remove this test @Disabled @Test public void currentlyFailingBingDecoding1() { var exception = assertThrows( Exception.class, () -> testTile( "5-16-9", TestSettings.BING_MVT_PATH, DecoderType.VECTORIZED, EncodingType.ADVANCED, false)); assertEquals( "java.lang.IllegalArgumentException: VectorType not supported yet: CONST", exception.toString()); } /* OpenMapTiles schema based vector tiles tests --------------------------------------------------------- */ // TODO: after https://github.com/maplibre/maplibre-tile-spec/issues/185 is fixed // remove this test and start testing all the OMT tiles with sorting enabled @Test @Disabled public void decodeOMTTilesSortedFail() { var exception = assertThrows( Exception.class, () -> testTile( "4_8_10", TestSettings.OMT_MVT_PATH, DecoderType.BOTH, EncodingType.ADVANCED, true)); assertEquals("java.lang.IndexOutOfBoundsException", exception.toString()); } private static Stream omtProvider() { return Stream.of( "2_2_2", "3_4_5", "4_8_10", "4_3_9", "5_16_21", "5_16_20", "6_32_41", "6_33_42", "7_66_84", "7_66_85", "8_134_171", "8_132_170", "9_265_341", "10_532_682", "11_1064_1367", "12_2132_2734", "13_4265_5467", "14_8298_10748", "14_8299_10748"); } @DisplayName("Decode OMT Tiles (advanced encodings, non-sorted)") @ParameterizedTest() @MethodSource("omtProvider") @Disabled public void decodeOMTTiles(String tileId) throws IOException { var result = testTile(tileId, TestSettings.OMT_MVT_PATH, DecoderType.BOTH, EncodingType.ADVANCED, false); assertEquals( 0, result.numErrorsAdvanced(), "Error for " + tileId + "/advanced: " + result.numErrorsAdvanced()); } @DisplayName("Decode OMT Tiles (non-advanced encodings)") @ParameterizedTest() @MethodSource("omtProvider") @Disabled public void decodeOMTTiles2(String tileId) throws IOException { if (Objects.equals(tileId, "13_4265_5467") || Objects.equals(tileId, "14_8298_10748") || Objects.equals(tileId, "14_8299_10748")) { // TODO remove this special case for these 3 tiles once this bug is fixed: // https://github.com/maplibre/maplibre-tile-spec/issues/183 var exception = assertThrows( Exception.class, () -> testTile( tileId, TestSettings.OMT_MVT_PATH, DecoderType.BOTH, EncodingType.NONADVANCED, false)); assertTrue(exception.toString().contains("ArrayIndexOutOfBoundsException")); } else { var result = testTile( tileId, TestSettings.OMT_MVT_PATH, DecoderType.BOTH, EncodingType.NONADVANCED, false); assertEquals( 0, result.numErrors(), "Error for " + tileId + "/advanced: " + result.numErrors()); } } /* Test utility functions */ private DecodingResult testTile( String tileId, String tileDirectory, DecoderType decoder, EncodingType encoding, boolean allowSorting) throws IOException { var mvtFilePath = Paths.get(tileDirectory, tileId + ".mvt"); var mvTile = MvtUtils.decodeMvt(mvtFilePath); final var columnMappings = new ColumnMappingConfig(); var tileMetadata = MltConverter.createTilesetMetadata( mvTile, columnMappings, true, ConversionConfig.TypeMismatchPolicy.COERCE); var allowIdRegeneration = false; var optimization = new FeatureTableOptimizations(allowSorting, allowIdRegeneration, List.of()); var optimizations = TestSettings.OPTIMIZED_MVT_LAYERS.stream() .collect(Collectors.toMap(l -> l, l -> optimization)); var includeIds = true; var mlTile = MltConverter.encode( mvTile, tileMetadata, ConversionConfig.builder() .includeIds(includeIds) .useFastPFOR(false) .useFSST(false) .optimizations(optimizations) .build(), null); var mlTileAdvanced = MltConverter.encode( mvTile, tileMetadata, ConversionConfig.builder() .includeIds(includeIds) .useFastPFOR(true) .useFSST(true) .optimizations(optimizations) .build(), null); int numErrors = -1; int numErrorsAdvanced = -1; if (decoder == DecoderType.SEQUENTIAL || decoder == DecoderType.BOTH) { if (encoding == EncodingType.ADVANCED || encoding == EncodingType.BOTH) { var decodedAdvanced = MltDecoder.decodeMlTile(mlTileAdvanced); numErrorsAdvanced += TestUtils.compareTilesSequential(decodedAdvanced, mvTile, allowSorting); } if (encoding == EncodingType.NONADVANCED || encoding == EncodingType.BOTH) { var decoded = MltDecoder.decodeMlTile(mlTile); numErrors += TestUtils.compareTilesSequential(decoded, mvTile, allowSorting); } } if (decoder == DecoderType.VECTORIZED || decoder == DecoderType.BOTH) { throw new NotImplementedException("Vectorized decoding is not available"); } return new DecodingResult(numErrors, numErrorsAdvanced); } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/decoder/StringDecoderTest.java ================================================ package org.maplibre.mlt.decoder; import java.io.IOException; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.regex.Pattern; import me.lemire.integercompression.IntWrapper; import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; import org.locationtech.jts.util.Assert; import org.maplibre.mlt.TestSettings; import org.maplibre.mlt.TestUtils; import org.maplibre.mlt.converter.ColumnMapping; import org.maplibre.mlt.converter.ColumnMappingConfig; import org.maplibre.mlt.converter.MltConverter; import org.maplibre.mlt.converter.encodings.StringEncoder; import org.maplibre.mlt.converter.mvt.MvtUtils; import org.maplibre.mlt.data.Feature; import org.maplibre.mlt.data.Layer; import org.maplibre.mlt.metadata.stream.PhysicalLevelTechnique; import org.maplibre.mlt.metadata.tileset.MltMetadata; import org.maplibre.mlt.util.ByteArrayUtil; import org.maplibre.mlt.util.StreamUtil; public class StringDecoderTest { public static Pair> encodeSharedDictionary( List> values, PhysicalLevelTechnique physicalLevelTechnique, boolean useFsstEncoding) throws IOException { return StringEncoder.encodeSharedDictionary(values, physicalLevelTechnique, useFsstEncoding); } @Test @Disabled("Dictionary decoding to a scalar column is not implemented yet") public void decodeSharedDictionary_FsstDictionaryEncoded() throws IOException { var values1 = List.of( "TestTestTestTestTestTest", "TestTestTestTestTestTest1", "TestTestTestTestTestTest2", "TestTestTestTestTestTest2", "TestTestTestTestTestTest4"); var values2 = List.of( "TestTestTestTestTestTest6", "TestTestTestTestTestTest5", "TestTestTestTestTestTest8", "TestTestTestTestTestTes9", "TestTestTestTestTestTest10"); var values = List.of(values1, values2); var encodedValues = encodeSharedDictionary(values, PhysicalLevelTechnique.FAST_PFOR, true); final var isNullable = true; final var tileMetadata = new MltMetadata.Column( new MltMetadata.Field( MltMetadata.scalarFieldType(MltMetadata.ScalarType.STRING, isNullable), "Test")); final var decodedValues = StringDecoder.decodeSharedDictionary( ByteArrayUtil.concat(encodedValues.getRight()), new IntWrapper(0), tileMetadata); final var v = decodedValues.getRight(); Assert.equals(values1, v.get(":Test")); Assert.equals(values2, v.get(":Test2")); } @Test public void decodeSharedDictionary_DictionaryEncoded() throws IOException { final var values1 = List.of("Test", "Test2", "Test4", "Test2", "Test"); final var values2 = List.of("Test1", "Test2", "Test1", "Test5", "Test"); final var values = List.of(values1, values2); final var encodedValues = encodeSharedDictionary(values, PhysicalLevelTechnique.FAST_PFOR, false); final var test = createField("Test", MltMetadata.ScalarType.STRING, true); final var test2 = createField("Test2", MltMetadata.ScalarType.STRING, true); final var tileMetadata = new MltMetadata.Column( new MltMetadata.Field(MltMetadata.structFieldType(List.of(test, test2)), "Parent")); final var decodedValues = StringDecoder.decodeSharedDictionary( ByteArrayUtil.concat(encodedValues.getRight()), new IntWrapper(0), tileMetadata); final var v = decodedValues.getRight(); Assert.equals(values1, v.get("ParentTest")); Assert.equals(values2, v.get("ParentTest2")); } private MltMetadata.Field createField( String name, @SuppressWarnings("SameParameterValue") MltMetadata.ScalarType type, boolean isNullable) { return new MltMetadata.Field(MltMetadata.scalarFieldType(type, isNullable), name); } private MltMetadata.ComplexField createComplexColumn(MltMetadata.Field... fields) { return new MltMetadata.ComplexField(MltMetadata.ComplexType.STRUCT, Arrays.asList(fields)); } @Test public void decodeSharedDictionary_NullValues_DictionaryEncoded() throws IOException { final var values1 = Arrays.asList("Test", null, "Test2", null, "Test4", "Test2", "Test"); final var values2 = Arrays.asList( null, "Test1", "Test2", "Test1", null, null, "Test5", null, "Test", null, null); final var values = List.of(values1, values2); final var encodedValues = encodeSharedDictionary(values, PhysicalLevelTechnique.FAST_PFOR, false); final var test = createField("Test", MltMetadata.ScalarType.STRING, true); final var test2 = createField("Test2", MltMetadata.ScalarType.STRING, true); final var tileMetadata = new MltMetadata.Column( new MltMetadata.Field(MltMetadata.structFieldType(List.of(test, test2)), "Parent")); final var decodeResults = StringDecoder.decodeSharedDictionary( ByteArrayUtil.concat(encodedValues.getRight()), new IntWrapper(0), tileMetadata); final var decodedPresentValues = decodeResults.getMiddle(); final var decodedValues = decodeResults.getRight(); final var actualValues1 = decodedValues.get("ParentTest"); final var p1 = decodedPresentValues.get("ParentTest"); final var decodedV1 = new ArrayList(); var counter = 0; for (var i = 0; i < decodedPresentValues.size(); i++) { decodedV1.add(p1.get(i) ? actualValues1.get(counter++) : null); } Assert.equals(decodedV1, new ArrayList<>(Arrays.asList("Test", null))); final var actualValues2 = decodedValues.get("ParentTest2"); final var p2 = decodedPresentValues.get("ParentTest2"); final var decodedV2 = new ArrayList(); var counter2 = 0; for (var i = 0; i < decodedPresentValues.size(); i++) { decodedV2.add(p2.get(i) ? actualValues2.get(counter2++) : null); } Assert.equals(decodedV2, new ArrayList<>(Arrays.asList(null, null))); Assert.equals(values1, decodedValues.get("ParentTest")); Assert.equals(values2, decodedValues.get("ParentTest2")); } @Test public void decodeSharedDictionary_NullValues_FsstDictionaryEncoded() throws IOException { final var values1 = Arrays.asList( null, null, null, null, "TestTestTestTestTestTest", "TestTestTestTestTestTest1", null, "TestTestTestTestTestTest2", "TestTestTestTestTestTest2", "TestTestTestTestTestTest4"); final var values2 = Arrays.asList( "TestTestTestTestTestTest6", null, "TestTestTestTestTestTest5", "TestTestTestTestTestTest8", "TestTestTestTestTestTes9", null, "TestTestTestTestTestTest10"); final var values = List.of(values1, values2); final var encodedValues = encodeSharedDictionary(values, PhysicalLevelTechnique.FAST_PFOR, true); final var test = createField("Test", MltMetadata.ScalarType.STRING, true); final var test2 = createField("Test2", MltMetadata.ScalarType.STRING, true); final var tileMetadata = new MltMetadata.Column( new MltMetadata.Field(MltMetadata.structFieldType(List.of(test, test2)), "Parent")); final var decodeResult = StringDecoder.decodeSharedDictionary( ByteArrayUtil.concat(encodedValues.getRight()), new IntWrapper(0), tileMetadata); final var decodedValues = decodeResult.getRight(); final var decodedPresentValues = decodeResult.getMiddle(); var actualValues1 = decodedValues.get("ParentTest"); var p1 = decodedPresentValues.get("ParentTest"); var decodedV1 = new ArrayList(); var counter = 0; for (var i = 0; i < decodedPresentValues.size(); i++) { decodedV1.add(p1.get(i) ? actualValues1.get(counter++) : null); } Assert.equals(decodedV1, new ArrayList<>(Arrays.asList(null, null))); var actualValues2 = decodedValues.get("ParentTest2"); var p2 = decodedPresentValues.get("ParentTest2"); var decodedV2 = new ArrayList(); var counter2 = 0; for (var i = 0; i < decodedPresentValues.size(); i++) { decodedV2.add(p2.get(i) ? actualValues2.get(counter2++) : null); } Assert.equals(decodedV2, new ArrayList<>(Arrays.asList("TestTestTestTestTestTest6", null))); Assert.equals(values1, decodedValues.get("ParentTest")); Assert.equals(values2, decodedValues.get("ParentTest2")); } @ParameterizedTest @EnumSource( value = PhysicalLevelTechnique.class, names = {"VARINT", "FAST_PFOR"}) public void decodeSharedDictionary_Mvt(PhysicalLevelTechnique technique) throws IOException { final var tileId = String.format("%s_%s_%s", 5, 16, 21); final var mvtFilePath = Paths.get(TestSettings.OMT_MVT_PATH, tileId + ".mvt"); final var mvTile = MvtUtils.decodeMvt(mvtFilePath); final var values = mvTile .getLayerStream() .findFirst() .orElseThrow(() -> new IllegalArgumentException("Expected at least one layer")) .features() .stream() .flatMap( feature -> feature .getPropertyStream() .map(p -> p.getValue(feature.getIndex())) .flatMap(StreamUtil.ofType(String.class))) .toList(); final var encodedValues = encodeSharedDictionary(List.of(values), technique, false); final var isNullable = true; final var tileMetadata = new MltMetadata.Column( new MltMetadata.Field( MltMetadata.structFieldType( List.of(createField("TestChild", MltMetadata.ScalarType.STRING, isNullable))), "TestParent:")); var decodeResult = StringDecoder.decodeSharedDictionary( ByteArrayUtil.concat(encodedValues.getRight()), new IntWrapper(0), tileMetadata); var decodedValues = decodeResult.getRight().get("TestParent:TestChild"); Assert.equals(values, decodedValues); } @ParameterizedTest @ValueSource(strings = {"water_name", "place"}) public void decodeSharedDictionary_MvtWithNestedColumns(String tableName) throws IOException { final var tileId = String.format("%s_%s_%s", 5, 16, 21); final var mvtFilePath = Paths.get(TestSettings.OMT_MVT_PATH, tileId + ".mvt"); // Force coverage of the case where the base mapped column does not appear in the first // feature, causing the metadata field to need to be modified when it is found on a later one. final var filter = new TestUtils.TileFilter() { private boolean matched = false; @Override public boolean test( Layer layer, Feature feature, String propertyKey, Object propertyValue) { if (!matched && layer.name().equals(tableName) && propertyKey.equals("name")) { matched = true; return false; } return true; } }; final var mvTile = TestUtils.filterTile(MvtUtils.decodeMvt(mvtFilePath), filter); final var columnMapping = new ColumnMapping("name", ":", true); final var columnMappings = ColumnMappingConfig.of(Pattern.compile(".*"), List.of(columnMapping)); final var tileMetadata = MltConverter.createTilesetMetadata(mvTile, columnMappings, true); final var featureTable = tileMetadata.featureTables.stream() .filter(t -> t.name().equals(tableName)) .findFirst() .orElseThrow( () -> new IllegalArgumentException("Expected feature table " + tableName)); final var fieldMetadata = featureTable.columns().stream() .filter(f -> Objects.equals(f.getName(), "name")) .findFirst() .orElseThrow(); final var layer = mvTile .getLayerStream() .filter(t -> t.name().equals(tableName)) .findFirst() .orElseThrow(() -> new IllegalArgumentException("Expected layer " + tableName)); final var sharedValues = new ArrayList>(fieldMetadata.field().type().complexType().children().size()); for (var column : fieldMetadata.field().type().complexType().children()) { var values = new ArrayList(); for (var feature : layer.features()) { values.add( feature .findProperty( fieldMetadata.getName() + column.name(), MltMetadata.ScalarType.STRING) .map(p -> p.getValue(feature.getIndex())) .flatMap(StreamUtil.optionalOfType(String.class)) .orElse(null)); } sharedValues.add(values); } final var encodedValues = encodeSharedDictionary(sharedValues, PhysicalLevelTechnique.FAST_PFOR, false); Assert.isTrue(encodedValues.getLeft() > 2); final var decodeResult = StringDecoder.decodeSharedDictionary( ByteArrayUtil.concat(encodedValues.getRight()), new IntWrapper(0), fieldMetadata); final var decodedValues = decodeResult.getRight(); for (var column : fieldMetadata.field().type().complexType().children()) { var i = 0; for (var feature : layer.features()) { final var propertyName = fieldMetadata.getName() + column.name(); final var expectedValue = feature .findProperty(propertyName, MltMetadata.ScalarType.STRING) .map(p -> p.getValue(feature.getIndex())) .flatMap(StreamUtil.optionalOfType(String.class)) .orElse(null); final var field = decodedValues.get(propertyName); Assert.isTrue(expectedValue == null || field != null); final var actualValue = field.get(i++); if (expectedValue != null || actualValue != null) { Assert.equals(expectedValue, actualValue); } } } } /// Apply multiple column mappings with the same prefix @Test public void decodeColumnMap_Mvt_prefix_multi() throws IOException { final var tileId = String.format("%s_%s_%s", 5, 16, 21); final var mvtFilePath = Paths.get(TestSettings.OMT_MVT_PATH, tileId + ".mvt"); final var mvTile = MvtUtils.decodeMvt(mvtFilePath); final var mapping1 = new ColumnMapping("name", ":", true); final var mapping2 = new ColumnMapping("name", "_", true); final var columnMappings = ColumnMappingConfig.of(Pattern.compile(".*"), List.of(mapping1, mapping2)); final var metadata = MltConverter.createTilesetMetadata(mvTile, columnMappings, /*isIdPresent*/ true); final var expected = Map.of( "water_name:name", 59, "water_name:name_", 3, "place:name", 68, "place:name_", 3); int found = 0; for (var table : metadata.featureTables) { for (var column : table.columns()) { if (column.is(MltMetadata.ComplexType.STRUCT)) { final var complex = column.field().type().complexType(); final var fieldKey = table.name() + ":" + column.getName(); Assert.equals( expected.get(fieldKey), complex.children().size(), "Unexpected number of children in " + fieldKey); found++; } } } Assert.equals(expected.size(), found); } /// Apply explicit column mappings @Test public void decodeColumnMap_Mvt_explicit() throws IOException { final var tileId = String.format("%s_%s_%s", 5, 16, 21); final var mvtFilePath = Paths.get(TestSettings.OMT_MVT_PATH, tileId + ".mvt"); final var mvTile = MvtUtils.decodeMvt(mvtFilePath); final var columnMapping = new ColumnMapping(List.of("name", "name:en", "name:latin", "name_en", "name_int"), true); final var columnMappings = ColumnMappingConfig.of(Pattern.compile(".*"), List.of(columnMapping)); final var metadata = MltConverter.createTilesetMetadata(mvTile, columnMappings, /*isIdPresent*/ true); final var expected = Map.of( "water_name:name", 5, "place:name", 5); int found = 0; for (var table : metadata.featureTables) { for (var column : table.columns()) { if (column.is(MltMetadata.ComplexType.STRUCT)) { final var complex = column.field().type().complexType(); final var fieldKey = table.name() + ":" + column.getName(); Assert.isTrue(expected.containsKey(fieldKey)); Assert.equals( expected.get(fieldKey), complex.children().size(), "Unexpected number of children in " + fieldKey); found++; } } } Assert.equals(expected.size(), found); } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/json/JsonTest.java ================================================ package org.maplibre.mlt.json; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; import org.maplibre.mlt.data.Feature; import org.maplibre.mlt.data.Layer; import org.maplibre.mlt.data.MLTFeature; import org.maplibre.mlt.data.MapLibreTile; import org.maplibre.mlt.data.MapboxVectorTile; class JsonTest { private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); @Test void propertyValues() { final Map properties = new HashMap<>(); properties.put("name", "Main St"); properties.put("nullValue", null); final var tile = mltOf( layer( "roads", 4096, feature(7, GEOMETRY_FACTORY.createPoint(new Coordinate(1, 2)), properties))); final JsonObject root = JsonParser.parseString(Json.toJson(tile, false)).getAsJsonObject(); final var layer = root.getAsJsonArray("layers").get(0).getAsJsonObject(); final var feature = layer.getAsJsonArray("features").get(0).getAsJsonObject(); assertEquals("roads", layer.get("name").getAsString()); assertEquals(4096, layer.get("extent").getAsInt()); assertEquals(7L, feature.get("id").getAsLong()); assertEquals("POINT (1 2)", feature.get("geometry").getAsString()); assertEquals("Main St", feature.getAsJsonObject("properties").get("name").getAsString()); assertFalse(feature.getAsJsonObject("properties").has("nullValue")); } @Test void mvtSerializesLayers() { final var tile = mvtOf( layer( "water", 4096, feature( GEOMETRY_FACTORY.createPoint(new Coordinate(5, 6)), Map.of("class", "river")))); final JsonObject root = JsonParser.parseString(Json.toJson(tile, false)).getAsJsonObject(); final var layer = root.getAsJsonArray("layers").get(0).getAsJsonObject(); assertEquals("water", layer.get("name").getAsString()); assertEquals(1, layer.getAsJsonArray("features").size()); assertEquals( "river", layer .getAsJsonArray("features") .get(0) .getAsJsonObject() .getAsJsonObject("properties") .get("class") .getAsString()); } @Test void featureCollectionWithLayerMetadata() { final var tile = mltOf( layer( "buildings", 8192, feature( 10, GEOMETRY_FACTORY.createPoint(new Coordinate(12, 34)), Map.of("height", 15)))); final JsonObject root = JsonParser.parseString(Json.toGeoJson(tile, false)).getAsJsonObject(); final var feature = root.getAsJsonArray("features").get(0).getAsJsonObject(); final var properties = feature.getAsJsonObject("properties"); assertEquals("FeatureCollection", root.get("type").getAsString()); assertEquals("Feature", feature.get("type").getAsString()); assertEquals(10L, feature.get("id").getAsLong()); assertEquals("buildings", properties.get("_layer").getAsString()); assertEquals(8192, properties.get("_extent").getAsInt()); assertEquals(15, properties.get("height").getAsInt()); assertEquals("Point", feature.getAsJsonObject("geometry").get("type").getAsString()); } @Test void floatAndDoublePropertiesToTokens() { final var nested = new LinkedHashMap(); nested.put("inner", Double.NEGATIVE_INFINITY); final var properties = new LinkedHashMap(); properties.put("f32nan", Float.NaN); properties.put("f32inf", Float.POSITIVE_INFINITY); properties.put("f32neg", Float.NEGATIVE_INFINITY); properties.put("f64nan", Double.NaN); properties.put("f64inf", Double.POSITIVE_INFINITY); properties.put("f64neg", Double.NEGATIVE_INFINITY); properties.put("f32", 1.5f); properties.put("f64", -1.5); final var tile = mltOf( layer( "roads", 4096, feature(1, GEOMETRY_FACTORY.createPoint(new Coordinate(0, 0)), properties))); final var props = JsonParser.parseString(Json.toGeoJson(tile, false)) .getAsJsonObject() .getAsJsonArray("features") .get(0) .getAsJsonObject() .getAsJsonObject("properties"); assertEquals("1.5", props.get("f32").getAsJsonPrimitive().toString()); assertEquals("f32::NAN", props.get("f32nan").getAsString()); assertEquals("f32::INFINITY", props.get("f32inf").getAsString()); assertEquals("f32::NEG_INFINITY", props.get("f32neg").getAsString()); assertEquals("-1.5", props.get("f64").getAsJsonPrimitive().toString()); assertEquals("f64::NAN", props.get("f64nan").getAsString()); assertEquals("f64::INFINITY", props.get("f64inf").getAsString()); assertEquals("f64::NEG_INFINITY", props.get("f64neg").getAsString()); } @Test void nullGeometry() { final var tile = mltOf(layer("places", 4096, feature(5, null, Map.of("name", "unknown")))); final var feature = JsonParser.parseString(Json.toGeoJson(tile, false)) .getAsJsonObject() .getAsJsonArray("features") .get(0) .getAsJsonObject(); assertFalse(feature.has("geometry")); } @Test void integerCoordinates() { final var line = GEOMETRY_FACTORY.createLineString( new Coordinate[] {new Coordinate(1.0, 2.0), new Coordinate(3.5, 4.0)}); final var tile = mltOf(layer("roads", 4096, feature(1, line, Map.of()))); final var coordinates = JsonParser.parseString(Json.toGeoJson(tile, false)) .getAsJsonObject() .getAsJsonArray("features") .get(0) .getAsJsonObject() .getAsJsonObject("geometry") .getAsJsonArray("coordinates"); assertEquals("1", coordinates.get(0).getAsJsonArray().get(0).getAsJsonPrimitive().toString()); assertEquals("2", coordinates.get(0).getAsJsonArray().get(1).getAsJsonPrimitive().toString()); assertEquals("3.5", coordinates.get(1).getAsJsonArray().get(0).getAsJsonPrimitive().toString()); assertEquals("4", coordinates.get(1).getAsJsonArray().get(1).getAsJsonPrimitive().toString()); } @Test void propertiesAlphabetical() { final var properties = new LinkedHashMap(); properties.put("zeta", 3); properties.put("alpha", 1); properties.put("middle", 2); final var tile = mltOf( layer( "roads", 4096, feature(1, GEOMETRY_FACTORY.createPoint(new Coordinate(0, 0)), properties))); final var json = Json.toGeoJson(tile, false); final var propertiesStart = json.indexOf("\"properties\":{"); final var geometryStart = json.indexOf(",\"geometry\"", propertiesStart); final var propertiesJson = json.substring(propertiesStart, geometryStart); final var alphaIndex = propertiesJson.indexOf("\"alpha\":"); final var middleIndex = propertiesJson.indexOf("\"middle\":"); final var zetaIndex = propertiesJson.indexOf("\"zeta\":"); assertTrue(alphaIndex >= 0); assertTrue(middleIndex >= 0); assertTrue(zetaIndex >= 0); assertTrue(alphaIndex < middleIndex); assertTrue(middleIndex < zetaIndex); } @Test void jsonElementStableOrder() { final var tile = mltOf( layer( "roads", 4096, feature( 7, GEOMETRY_FACTORY.createPoint(new Coordinate(1, 2)), Map.of("name", "A")))); final var json = Json.toJson(tile, false); final var layersIndex = json.indexOf("\"layers\":"); final var nameIndex = json.indexOf("\"name\":", layersIndex); final var extentIndex = json.indexOf("\"extent\":", nameIndex); final var featuresIndex = json.indexOf("\"features\":", extentIndex); final var idIndex = json.indexOf("\"id\":", featuresIndex); final var geometryIndex = json.indexOf("\"geometry\":", idIndex); final var propertiesIndex = json.indexOf("\"properties\":", geometryIndex); assertTrue(layersIndex >= 0); assertTrue(nameIndex > layersIndex); assertTrue(extentIndex > nameIndex); assertTrue(featuresIndex > extentIndex); assertTrue(idIndex > featuresIndex); assertTrue(geometryIndex > idIndex); assertTrue(propertiesIndex > geometryIndex); } private static MapLibreTile mltOf(Layer... layers) { return new MapLibreTile(List.of(layers)); } private static MapboxVectorTile mvtOf(Layer... layers) { return new MapboxVectorTile(List.of(layers)); } private static Layer layer(String name, int extent, Feature... features) { return new Layer(name, List.of(features), extent); } private static Feature feature(Geometry geometry, Map properties) { return MLTFeature.builder().geometry(geometry).rawProperties(properties).index(0).build(); } private static Feature feature(long id, Geometry geometry, Map properties) { return MLTFeature.builder() .id(id) .geometry(geometry) .rawProperties(properties) .index(0) .build(); } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/metadata/tileset/MltMetadataColumnTest.java ================================================ package org.maplibre.mlt.metadata.tileset; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; import org.junit.jupiter.api.Test; class MltMetadataColumnTest { @Test void createScalarColumn() { final var col = new MltMetadata.Column( new MltMetadata.Field( MltMetadata.scalarFieldType(MltMetadata.ScalarType.STRING, true), "name")); assertEquals("name", col.getName()); assertTrue(col.isNullable()); assertEquals(MltMetadata.ColumnScope.FEATURE, col.columnScope()); assertNotNull(col.field().type().scalarType()); assertNull(col.field().type().complexType()); } @Test void createComplexColumn() { final var child = new MltMetadata.Field(MltMetadata.scalarFieldType(MltMetadata.ScalarType.INT_32, false)); final var col = new MltMetadata.Column( new MltMetadata.Field(MltMetadata.structFieldType(List.of(child)), "geom"), MltMetadata.ColumnScope.VERTEX); assertEquals("geom", col.getName()); assertFalse(col.isNullable()); assertEquals(MltMetadata.ColumnScope.VERTEX, col.columnScope()); assertNotNull(col.field().type().complexType()); assertNull(col.field().type().scalarType()); assertEquals(MltMetadata.ComplexType.STRUCT, col.field().type().complexType().physicalType()); assertEquals(1, col.field().type().complexType().children().size()); } @Test void acceptsNullChildren() { final var col = new MltMetadata.Column(MltMetadata.structFieldType(null)); assertNotNull(col.field().type().complexType()); assertEquals(MltMetadata.ComplexType.STRUCT, col.field().type().complexType().physicalType()); assertTrue(col.field().type().complexType().children().isEmpty()); } @Test void defaultsToFeatureScope() { final var col = new MltMetadata.Column(MltMetadata.scalarFieldType(MltMetadata.ScalarType.STRING, true)); assertEquals(MltMetadata.ColumnScope.FEATURE, col.columnScope()); } @Test void rejectsInvalidScope() { assertThrows( IllegalStateException.class, () -> new MltMetadata.Column( new MltMetadata.Field( MltMetadata.scalarFieldType(MltMetadata.ScalarType.STRING, true)), MltMetadata.ColumnScope.UNRECOGNIZED)); } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/synthetics/SyntheticsTest.java ================================================ package org.maplibre.mlt.synthetics; import com.google.gson.GsonBuilder; import com.google.gson.ToNumberPolicy; import java.io.IOException; import java.math.BigDecimal; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.util.Arrays; import java.util.Map; import java.util.Objects; import java.util.SequencedCollection; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.maplibre.mlt.data.MapLibreTile; import org.maplibre.mlt.decoder.MltDecoder; import org.maplibre.mlt.json.Json; public class SyntheticsTest { @Test public void checkSynthetics() throws IOException { final var matcher = FileSystems.getDefault().getPathMatcher("glob:**/*.mlt"); final var rootPaths = new String[] {"../../test/synthetic/0x01", "../../test/synthetic/0x01-rust"}; final var mltPaths = Arrays.stream(rootPaths).flatMap(root -> findFiles(root, matcher).stream()).toList(); for (Path path : mltPaths) { // Load the file final MapLibreTile tile; try { tile = MltDecoder.decodeMlTile(Files.readAllBytes(path)); } catch (Exception e) { Assertions.fail("Failed to decode " + path, e); continue; } final var baseName = path.getFileName().toString(); final var jsonPath = path.resolveSibling( baseName.substring(0, Math.max(0, baseName.lastIndexOf('.'))) + ".json"); if (!Files.exists(jsonPath)) { System.err.println("WARNING: No expected JSON for " + path); continue; } // Don't let Gson turn Uin64 values into doubles, which loses precision. final var gson = new GsonBuilder() .serializeSpecialFloatingPointValues() .setObjectToNumberStrategy(ToNumberPolicy.BIG_DECIMAL) .create(); Object expectedJsonObjects; try (var jsonReader = Files.newBufferedReader(jsonPath)) { expectedJsonObjects = gson.fromJson(jsonReader, Object.class); } catch (Exception e) { Assertions.fail("Failed to read " + jsonPath, e); continue; } final var actualJsonObjects = Json.toGeoJsonObjects(tile, gson); Assertions.assertTrue( compareJsonObjects(expectedJsonObjects, actualJsonObjects), "JSON objects do not match for " + path); } } private static SequencedCollection findFiles(String root, PathMatcher matcher) { try (var paths = Files.walk(Path.of(root))) { return paths.filter(matcher::matches).filter(Files::isRegularFile).toList(); } catch (IOException e) { throw new RuntimeException("Failed to walk " + root, e); } } private boolean compareJsonObjects(Object expected, Object actual) { if (expected instanceof Map expectedMap && actual instanceof Map actualMap) { if (expectedMap.size() != actualMap.size()) { return false; } for (var entry : expectedMap.entrySet()) { final var key = entry.getKey(); if (!actualMap.containsKey(key)) { return false; } if (!compareJsonObjects(entry.getValue(), actualMap.get(key))) { return false; } } return true; } else if (expected instanceof Iterable expectedIterable && actual instanceof Iterable actualIterable) { final var expectedIterator = expectedIterable.iterator(); final var actualIterator = actualIterable.iterator(); while (expectedIterator.hasNext() && actualIterator.hasNext()) { if (!compareJsonObjects(expectedIterator.next(), actualIterator.next())) { return false; } } return !expectedIterator.hasNext() && !actualIterator.hasNext(); } return Objects.equals(expected, actual) || numericsEqual(expected, actual); } private boolean numericsEqual(Object a, Object b) { if (a instanceof Number numA && b instanceof Number numB) { if (numA.doubleValue() == numB.doubleValue()) { return true; } else if (compareFloats(numA, numB) || compareFloats(numB, numA)) { return true; } else if (a instanceof BigDecimal || b instanceof BigDecimal) { return compareDecimals(a, b) || compareDecimals(b, a); } } return false; } private boolean compareFloats(Number a, Number b) { if (a instanceof Double dbl && b instanceof Float flt) { return dbl.floatValue() == flt; } return false; } /// Compare values loaded as BigDecimal, with /// particular care for unsigned values loaded as, e.g., -1 private boolean compareDecimals(Object a, Object b) { if (a instanceof BigDecimal aDec) { if (b instanceof BigDecimal bDec) { return aDec.compareTo(bDec) == 0; } else if (b instanceof Float fltB) { return aDec.floatValue() == fltB || aDec.compareTo(BigDecimal.valueOf(fltB)) == 0; } else if (b instanceof Number numB) { return numB.intValue() == aDec.intValue() || numB.longValue() == aDec.longValue() || aDec.compareTo(BigDecimal.valueOf(numB.longValue())) == 0 || aDec.compareTo(BigDecimal.valueOf(numB.doubleValue())) == 0; } } return false; } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/util/OptionalUtilTest.java ================================================ package org.maplibre.mlt.util; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Optional; import org.junit.jupiter.api.Test; class OptionalUtilTest { @Test void isLessThanBothEmpty() { assertFalse(OptionalUtil.isLessThan(Optional.empty(), Optional.empty())); } @Test void isLessThan_whenLeftEmptyAndRightPresent_returnsFalse() { assertFalse(OptionalUtil.isLessThan(Optional.empty(), Optional.of(10))); } @Test void isLessThan_whenLeftPresentAndRightEmpty_returnsTrue() { assertTrue(OptionalUtil.isLessThan(Optional.of(10), Optional.empty())); } @Test void isLessThanLess() { assertTrue(OptionalUtil.isLessThan(Optional.of(10), Optional.of(20))); } @Test void isLessThanEqual() { assertFalse(OptionalUtil.isLessThan(Optional.of(20), Optional.of(20))); } @Test void isLessThanGreater() { assertFalse(OptionalUtil.isLessThan(Optional.of(30), Optional.of(20))); } @Test void isLessThanSupportsComparableTypes() { assertTrue(OptionalUtil.isLessThan(Optional.of("a"), Optional.of("b"))); assertFalse(OptionalUtil.isLessThan(Optional.of("b"), Optional.of("a"))); } @Test void isLessThanThrowsWhenOptional1Nulls() { assertThrows(NullPointerException.class, () -> OptionalUtil.isLessThan(null, Optional.of(1))); } @Test void isLessThanThrowsWhenOptional2Null() { assertThrows(NullPointerException.class, () -> OptionalUtil.isLessThan(Optional.of(1), null)); } @Test void mapReturnsMappedValue() { final var result = OptionalUtil.map(Optional.of(2), Optional.of(3), Integer::sum); assertEquals(Optional.of(5), result); } @Test void mapEmptyWhenOptional1Empty() { final var result = OptionalUtil.map(Optional.empty(), Optional.of(3), Integer::sum); assertEquals(Optional.empty(), result); } @Test void mapEmptyWhenOptional2Empty() { final var result = OptionalUtil.map(Optional.of(2), Optional.empty(), Integer::sum); assertEquals(Optional.empty(), result); } @Test void mapBothEmpty() { final var result = OptionalUtil.map(Optional.empty(), Optional.empty(), Integer::sum); assertEquals(Optional.empty(), result); } @Test void mapThrowsWhenOptional1Null() { assertThrows( NullPointerException.class, () -> OptionalUtil.map(null, Optional.of(1), Integer::sum)); } @Test void mapThrowsWhenOptional2Null() { assertThrows( NullPointerException.class, () -> OptionalUtil.map(Optional.of(1), null, Integer::sum)); } @Test void mapThrowsWhenFunctionNull() { assertThrows( NullPointerException.class, () -> OptionalUtil.map(Optional.of(1), Optional.of(2), null)); } } ================================================ FILE: java/mlt-core/src/test/java/org/maplibre/mlt/util/StreamUtilTest.java ================================================ package org.maplibre.mlt.util; import static org.junit.jupiter.api.Assertions.*; import java.util.ArrayList; import java.util.stream.Stream; import java.util.stream.StreamSupport; import org.apache.commons.lang3.mutable.MutableBoolean; import org.apache.commons.lang3.mutable.MutableInt; import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.Test; class StreamUtilTest { @Test void zipWithEqualSizedStreams() { final var result = StreamUtil.zip(Stream.of(1, 2, 3), Stream.of("a", "b", "c"), (x, y) -> x + ":" + y) .toList(); assertEquals(3, result.size()); assertEquals("1:a", result.get(0)); assertEquals("2:b", result.get(1)); assertEquals("3:c", result.get(2)); } @Test void zipPairs() { final var result = StreamUtil.zip(Stream.of(1, 2, 3), Stream.of("a", "b", "c")).toList(); assertEquals(3, result.size()); assertEquals(Pair.of(1, "a"), result.get(0)); assertEquals(Pair.of(2, "b"), result.get(1)); assertEquals(Pair.of(3, "c"), result.get(2)); } @Test void zipWithEmptyStreams() { assertEquals(0, StreamUtil.zip(Stream.of(), Stream.of(), (x, y) -> 0).toList().size()); } @Test void zipWithFirstStreamShorter() { final var result = StreamUtil.zip(Stream.of(1, 2), Stream.of("a", "b", "c"), (x, y) -> x + ":" + y).toList(); assertEquals(2, result.size()); assertEquals("1:a", result.get(0)); assertEquals("2:b", result.get(1)); } @Test void zipWithSecondStreamShorter() { final var result = StreamUtil.zip(Stream.of(1, 2, 3), Stream.of("a", "b"), (x, y) -> x + ":" + y).toList(); assertEquals(2, result.size()); assertEquals("1:a", result.get(0)); assertEquals("2:b", result.get(1)); } @Test void zipWithTransformationFunction() { final var result = StreamUtil.zip(Stream.of(10, 20, 30), Stream.of(1, 2, 3), (x, y) -> x + y).toList(); assertEquals(3, result.size()); assertEquals(11, result.get(0)); assertEquals(22, result.get(1)); assertEquals(33, result.get(2)); } @Test void zipPreservesStreamCharacteristics() { final var result = StreamUtil.zip(Stream.of(1, 2, 3), Stream.of("a", "b", "c"), (x, y) -> x + ":" + y) .toList(); assertNotNull(result); assertEquals(3, result.size()); } void zipWithParallelStreams(boolean first, boolean second) { final var a = Stream.of(1, 2, 3, 4, 5).parallel(); final var b = Stream.of("a", "b", "c", "d", "e").parallel(); final var result = StreamUtil.zip(first ? a.parallel() : a, second ? b.parallel() : b, (x, y) -> x + ":" + y) .toList(); // Results should contain all expected pairs (order may vary due to parallel processing) assertEquals(5, result.size()); assertTrue(result.contains("1:a")); assertTrue(result.contains("2:b")); assertTrue(result.contains("3:c")); assertTrue(result.contains("4:d")); assertTrue(result.contains("5:e")); } @Test void zipWithFirstParallelStream() { zipWithParallelStreams(true, false); } @Test void zipWithSecondParallelStream() { zipWithParallelStreams(false, true); } @Test void zipWithBothParallelStreams() { zipWithParallelStreams(true, true); } @Test void zipWithNullTransformationFunction() { assertThrows( NullPointerException.class, () -> StreamUtil.zip(Stream.of(1, 2, 3), Stream.of("a", "b", "c"), null)); } @Test void zipWithNullFirstStream() { assertThrows( NullPointerException.class, () -> StreamUtil.zip(null, Stream.of(), (x, y) -> x + ":" + y)); } @Test void zipWithNullSecondStream() { assertThrows( NullPointerException.class, () -> StreamUtil.zip(Stream.of(), null, (x, y) -> x + ":" + y)); } @Test void zipEachProcessesAllPairs() { final var processed = new ArrayList<>(); final long count = StreamUtil.zipEach( Stream.of(1, 2, 3), Stream.of("a", "b", "c"), (x, y) -> processed.add(x + ":" + y)); assertEquals(3, count); } @Test void zipEachWithEmptyStreams() { assertEquals(0, StreamUtil.zipEach(Stream.of(), Stream.of(), (x, y) -> {})); } @Test void zipEachWithMismatchedKnownStreamSizes() { final var sideEffectCount = new MutableInt(0); final long count = StreamUtil.zipEach( Stream.of(1, 2, 3), Stream.of("a", "b"), (x, y) -> sideEffectCount.increment()); // With the new behavior, we process all pairs from the shorter stream assertEquals(2, count); assertEquals(2, sideEffectCount.get()); } @Test void zipEachWithMismatchedUnknownStreamSizes() { final var sideEffectCount = new MutableInt(0); final var a = Stream.iterate(0, i -> i < 3, i -> i + 1); final var b = Stream.iterate(0, i -> i < 2, i -> i + 1); // Streams with different sizes now silently truncate to the shorter stream final long count = StreamUtil.zipEach(a, b, (x, y) -> sideEffectCount.increment()); // With the new behavior, we process all pairs from the shorter stream assertEquals(2, count); assertEquals(2, sideEffectCount.get()); } @Test void zipEachWithNullFirstStream() { assertThrows( NullPointerException.class, () -> StreamUtil.zipEach(null, Stream.of(), (x, y) -> {})); } @Test void zipEachWithNullSecondStream() { assertThrows( NullPointerException.class, () -> StreamUtil.zipEach(Stream.of(), null, (x, y) -> {})); } @Test void zipWithLargeStreams() { final var a = Stream.iterate(0, i -> i + 1).limit(1000); final var b = Stream.iterate(1000, i -> i + 1).limit(1000); final var result = StreamUtil.zip(a, b, Integer::sum).toList(); assertEquals(1000, result.size()); assertEquals(1000, result.get(0)); // First pair: 0 + 1000 = 1000 assertEquals(1008, result.get(4)); // Fifth pair: 4 + 1004 = 1008 assertEquals(2998, result.get(999)); // Last pair: 999 + 1999 = 2998 } @Test void zipHandlesNullValuesInStreams() { final var result = StreamUtil.zip(Stream.of("a", null, "c"), Stream.of("1", "2", "3"), (x, y) -> x + ":" + y) .toList(); assertEquals(3, result.size()); assertEquals("a:1", result.get(0)); assertEquals("null:2", result.get(1)); assertEquals("c:3", result.get(2)); } @Test void zipWithDifferentObjectTypes() { final var result = StreamUtil.zip( Stream.of("one", "two", "three"), Stream.of(1.5, 2.5, 3.5), (s, d) -> s + "=" + d) .toList(); assertEquals(3, result.size()); assertEquals("one=1.5", result.get(0)); assertEquals("two=2.5", result.get(1)); assertEquals("three=3.5", result.get(2)); } @Test void zipStreamIsLazy() { final var functionCalled = new MutableBoolean(false); final var zipped = StreamUtil.zip( Stream.of(1, 2, 3), Stream.of("a", "b", "c"), (x, y) -> { functionCalled.setTrue(); return x + ":" + y; }); final var split = zipped.spliterator(); // size is known before evaluation for fixed-size inputs assertEquals(3, split.getExactSizeIfKnown()); // Function should not be called yet - stream is lazy assertFalse(functionCalled.get()); // Function should be called when stream is consumed final var result = StreamSupport.stream(split, false).toList(); assertTrue(functionCalled.get()); assertEquals(3, result.size()); assertEquals("3:c", result.get(2)); } @Test void zipWithInfiniteFirstStream() { final var infiniteStream = Stream.generate(() -> "x"); final var finiteStream = Stream.of(1, 2, 3, 4, 5); final var result = StreamUtil.zip(infiniteStream, finiteStream, (x, y) -> y + ":" + x).toList(); assertEquals(5, result.size()); assertEquals("1:x", result.get(0)); assertEquals("5:x", result.get(4)); } @Test void zipWithInfiniteSecondStream() { // Test with Stream.generate - an infinite stream as the second argument final var finiteStream = Stream.of("a", "b", "c"); final var infiniteStream = Stream.generate(() -> 42); final var result = StreamUtil.zip(finiteStream, infiniteStream, (x, y) -> x + ":" + y).toList(); assertEquals(3, result.size()); assertEquals("a:42", result.get(0)); assertEquals("c:42", result.get(2)); } } ================================================ FILE: java/mlt-tools/build.gradle ================================================ plugins { alias(libs.plugins.diffplug.spotless) id 'java-library' } repositories { mavenCentral() maven { url = 'https://maven.ecc.no/releases' } maven { url = 'https://repo.osgeo.org/repository/release/' content { includeGroup 'org.geotools' includeGroup 'org.geotools.ogc' includeGroup 'org.eclipse.imagen' includeGroup 'it.geosolutions.jgridshift' } } } dependencies { implementation project(':mlt-core') implementation(libs.jts.core) } java { toolchain { languageVersion = JavaLanguageVersion.of(21) } } spotless { java { importOrder() target 'src/*/java/**/*.java' googleJavaFormat() removeUnusedImports() } } gradle.projectsEvaluated { tasks.withType(JavaCompile).tap { configureEach { options.compilerArgs << "-Xlint:unchecked" options.compilerArgs << "-Xlint:deprecation" options.compilerArgs << "-Xlint:all" } } } tasks.register('generateSyntheticMlt', JavaExec) { description = 'Generates synthetic MLT test fixtures' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.maplibre.mlt.tools.SyntheticMltGenerator' workingDir = project.projectDir.parentFile } ================================================ FILE: java/mlt-tools/src/main/java/org/maplibre/mlt/tools/SyntheticMltGenerator.java ================================================ package org.maplibre.mlt.tools; import static org.maplibre.mlt.converter.ConversionConfig.IntegerEncodingOption.DELTA; import static org.maplibre.mlt.converter.ConversionConfig.IntegerEncodingOption.DELTA_RLE; import static org.maplibre.mlt.converter.ConversionConfig.IntegerEncodingOption.PLAIN; import static org.maplibre.mlt.converter.ConversionConfig.IntegerEncodingOption.RLE; import static org.maplibre.mlt.tools.SyntheticMltUtil.SYNTHETICS_DIR; import static org.maplibre.mlt.tools.SyntheticMltUtil.array; import static org.maplibre.mlt.tools.SyntheticMltUtil.c; import static org.maplibre.mlt.tools.SyntheticMltUtil.c1; import static org.maplibre.mlt.tools.SyntheticMltUtil.c2; import static org.maplibre.mlt.tools.SyntheticMltUtil.c3; import static org.maplibre.mlt.tools.SyntheticMltUtil.cfg; import static org.maplibre.mlt.tools.SyntheticMltUtil.feat; import static org.maplibre.mlt.tools.SyntheticMltUtil.gf; import static org.maplibre.mlt.tools.SyntheticMltUtil.idFeat; import static org.maplibre.mlt.tools.SyntheticMltUtil.kv; import static org.maplibre.mlt.tools.SyntheticMltUtil.layer; import static org.maplibre.mlt.tools.SyntheticMltUtil.line; import static org.maplibre.mlt.tools.SyntheticMltUtil.line1; import static org.maplibre.mlt.tools.SyntheticMltUtil.line2; import static org.maplibre.mlt.tools.SyntheticMltUtil.mortonCurve; import static org.maplibre.mlt.tools.SyntheticMltUtil.multi; import static org.maplibre.mlt.tools.SyntheticMltUtil.p0; import static org.maplibre.mlt.tools.SyntheticMltUtil.p1; import static org.maplibre.mlt.tools.SyntheticMltUtil.p2; import static org.maplibre.mlt.tools.SyntheticMltUtil.p3; import static org.maplibre.mlt.tools.SyntheticMltUtil.ph1; import static org.maplibre.mlt.tools.SyntheticMltUtil.ph2; import static org.maplibre.mlt.tools.SyntheticMltUtil.ph3; import static org.maplibre.mlt.tools.SyntheticMltUtil.poly; import static org.maplibre.mlt.tools.SyntheticMltUtil.poly1; import static org.maplibre.mlt.tools.SyntheticMltUtil.poly1h; import static org.maplibre.mlt.tools.SyntheticMltUtil.poly2; import static org.maplibre.mlt.tools.SyntheticMltUtil.prop; import static org.maplibre.mlt.tools.SyntheticMltUtil.props; import static org.maplibre.mlt.tools.SyntheticMltUtil.ring; import static org.maplibre.mlt.tools.SyntheticMltUtil.write; import java.io.IOException; import java.math.BigInteger; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.MultiPolygon; import org.locationtech.jts.geom.Polygon; import org.maplibre.mlt.data.Feature; import org.maplibre.mlt.data.Layer; import org.maplibre.mlt.data.unsigned.U32; import org.maplibre.mlt.data.unsigned.U64; public class SyntheticMltGenerator { public static void main(String[] args) throws IOException { if (Files.exists(SYNTHETICS_DIR)) { throw new IOException( "Synthetics dir must be deleted before running `:mlt-tools:generateSyntheticMlt`: " + SYNTHETICS_DIR.toAbsolutePath()); } Files.createDirectories(SYNTHETICS_DIR); generatePoints(); generateLines(); generatePolygons(); generateMultiPoints(); generateMultiLineStrings(); generateMixed(); generateExtent(); generateIds(); generateProperties(); generateSharedDictionaries(); generateFpfAlignments(); } private static void generatePoints() throws IOException { write("point", feat(p0), cfg()); } private static void generateLines() throws IOException { write("line", feat(line1), cfg()); write("line_morton_curve_no_morton", feat(line(mortonCurve)), cfg()); write("line_morton_curve_morton", feat(line(mortonCurve)), cfg().morton()); write("line_zero_length", feat(line(c(6, 6), c(6, 6))), cfg()); } private static void generatePolygons() throws IOException { var pol = feat(poly1); write("poly", pol, cfg()); write("poly_fpf", pol, cfg().fastPFOR()); write("poly_tes", pol, cfg().tessellate()); write("poly_fpf_tes", pol, cfg().fastPFOR().tessellate()); var polCollinear = feat(poly(c(0, 0), c(10, 0), c(20, 0), c(0, 0))); write("poly_collinear", polCollinear, cfg()); write("poly_collinear_fpf", polCollinear, cfg().fastPFOR()); write("poly_collinear_tes", polCollinear, cfg().tessellate()); write("poly_collinear_fpf_tes", polCollinear, cfg().fastPFOR().tessellate()); var polSelfIntersect = feat(poly(c(0, 0), c(10, 10), c(0, 10), c(10, 0), c(0, 0))); write("poly_self_intersect", polSelfIntersect, cfg()); write("poly_self_intersect_fpf", polSelfIntersect, cfg().fastPFOR()); write("poly_self_intersect_tes", polSelfIntersect, cfg().tessellate()); write("poly_self_intersect_fpf_tes", polSelfIntersect, cfg().fastPFOR().tessellate()); // Polygon with hole var polWithHole = feat(poly1h); write("poly_hole", polWithHole, cfg()); write("poly_hole_fpf", polWithHole, cfg().fastPFOR()); write("poly_hole_tes", polWithHole, cfg().tessellate()); write("poly_hole_fpf_tes", polWithHole, cfg().fastPFOR().tessellate()); var polyHoleTouching = feat( poly( ring(c(0, 0), c(10, 0), c(10, 10), c(0, 10), c(0, 0)), ring(c(0, 0), c(2, 2), c(5, 2), c(0, 0)))); write("poly_hole_touching", polyHoleTouching, cfg()); write("poly_hole_touching_fpf", polyHoleTouching, cfg().fastPFOR()); write("poly_hole_touching_tes", polyHoleTouching, cfg().tessellate()); write("poly_hole_touching_fpf_tes", polyHoleTouching, cfg().fastPFOR().tessellate()); // MultiPolygon var multiPol = feat(multi(poly1, poly2)); write("poly_multi", multiPol, cfg()); write("poly_multi_fpf", multiPol, cfg().fastPFOR()); write("poly_multi_tes", multiPol, cfg().tessellate()); write("poly_multi_fpf_tes", multiPol, cfg().fastPFOR().tessellate()); // Close the shared Morton curve into a ring to test Morton encoding for polygons. var mortonRing = Arrays.copyOf(mortonCurve, mortonCurve.length + 1); mortonRing[mortonCurve.length] = mortonRing[0]; write("poly_morton_ring_no_morton", feat(poly(mortonRing)), cfg()); write("poly_morton_ring_morton", feat(poly(mortonRing)), cfg().morton()); // Split the Morton curve into two halves (bottom 2 rows / top 2 rows of the 4x4 grid) // and close each into a ring to form a MultiPolygon. var half = mortonCurve.length / 2; var mortonRing1 = Arrays.copyOf(mortonCurve, half + 1); mortonRing1[half] = mortonRing1[0]; var mortonRing2src = Arrays.copyOfRange(mortonCurve, half, mortonCurve.length); var mortonRing2 = Arrays.copyOf(mortonRing2src, mortonRing2src.length + 1); mortonRing2[mortonRing2src.length] = mortonRing2[0]; write( "poly_multi_morton_ring_no_morton", feat(multi(poly(mortonRing1), poly(mortonRing2))), cfg()); write( "poly_multi_morton_ring_morton", feat(multi(poly(mortonRing1), poly(mortonRing2))), cfg().morton()); // Split the Morton curve at a different place so that the rings are different lengths, // use one as the shell and one as the hole of a single and multi-polygon. final var quarter = mortonCurve.length / 4; mortonRing1 = Arrays.copyOf(mortonCurve, quarter + 1); mortonRing1[quarter] = mortonRing1[0]; mortonRing2src = Arrays.copyOfRange(mortonCurve, quarter, mortonCurve.length); mortonRing2 = Arrays.copyOf(mortonRing2src, mortonRing2src.length + 1); mortonRing2[mortonRing2src.length] = mortonRing2[0]; write( "poly_morton_hole_morton", feat(poly(ring(mortonRing1), ring(mortonRing2))), cfg().morton()); write( "poly_multi_morton_hole_morton", feat(multi(poly(ring(mortonRing1), ring(mortonRing2)))), cfg().morton()); } private static void generateMultiPoints() throws IOException { write("multipoint", feat(multi(c1, c2, c3)), cfg()); // Morton-encoded multipoint: same curve used by multiline_morton and poly_morton tests. var half = mortonCurve.length / 2; var mortonPts1 = Arrays.copyOf(mortonCurve, half); write("multipoint_morton", feat(multi(mortonPts1)), cfg().morton()); } private static void generateMultiLineStrings() throws IOException { write("multiline", feat(multi(line1, line2)), cfg()); var half = mortonCurve.length / 2; var mortonLine1 = Arrays.copyOf(mortonCurve, half); var mortonLine2 = Arrays.copyOfRange(mortonCurve, half, mortonCurve.length); write("multiline_morton", feat(multi(line(mortonLine1), line(mortonLine2))), cfg().morton()); } record GeomType(String sym, Feature feat) {} private static void generateMixCombination(List current) throws IOException { var name = "mix_" + current.size() + "_" + current.stream().map(GeomType::sym).collect(Collectors.joining("_")); var feats = current.stream().map(t -> t.feat).toArray(Feature[]::new); write(layer(name, feats), cfg().geomEnc(PLAIN)); // if all geometries are of polygon type, add tessellated variant if (current.stream() .allMatch( t -> { Geometry geo = t.feat.getGeometry(); return geo instanceof Polygon || geo instanceof MultiPolygon; })) { write(layer(name + "_tes", feats), cfg().geomEnc(PLAIN).tessellate()); } } private static void generateMixedCombine(GeomType[] arr, int k, int start, List current) throws IOException { if (current.size() == k) { generateMixCombination(current); } else { for (int i = start; i < arr.length; i++) { if (i > start && arr[i] == arr[i - 1]) { continue; } current.add(arr[i]); generateMixedCombine(arr, k, i + 1, current); current.removeLast(); } } } private static void generateMixed() throws IOException { GeomType[] types = { new GeomType("pt", feat(gf.createPoint(c(38, 29)))), new GeomType("line", feat(line(c(5, 38), c(12, 45), c(9, 70)))), new GeomType("poly", feat(poly(c(55, 5), c(58, 28), c(75, 22), c(55, 5)))), new GeomType( "polyh", feat( poly( ring(c(52, 35), c(14, 55), c(60, 72), c(52, 35)), ring(c(32, 50), c(36, 60), c(24, 54), c(32, 50))))), new GeomType("mpt", feat(multi(c(6, 25), c(21, 41), c(23, 69)))), new GeomType( "mline", feat(multi(line(c(24, 10), c(42, 18)), line(c(30, 36), c(48, 52), c(35, 62))))), new GeomType( "mpoly", feat( multi( poly( ring(c(7, 20), c(21, 31), c(26, 9), c(7, 20)), ring(c(15, 20), c(20, 15), c(18, 25), c(15, 20))), poly(c(69, 57), c(71, 66), c(73, 64), c(69, 57))))), }; for (int k = 2; k <= types.length; k++) { generateMixedCombine(types, k, 0, new ArrayList<>()); } for (var a : types) { // Create A-A variants generateMixCombination(List.of(a, a)); for (var b : types) { if (!a.sym.equals(b.sym)) { // Create A-B-A variants generateMixCombination(List.of(a, b, a)); } } } } private static void generateExtent() throws IOException { int[] extents = {512, 4096, 131072, 1073741824}; for (int e : extents) { write(layer("extent_" + e, e, feat(line(c(0, 0), c(e - 1, e - 1)))), cfg()); write(layer("extent_buf_" + e, e, feat(line(c(-42, -42), c(e + 42, e + 42)))), cfg()); } } private static void generateIds() throws IOException { write("id", idFeat(100), cfg().ids()); write("id_min", idFeat(0), cfg().ids()); // FIXME: serialises as -1 // write("id_max", idFeat(0xFFFFFFFF), cfg().ids()); write("id64", idFeat(9_234_567_890L), cfg().ids()); // FIXME: writes as Id32 instead of Id64 // write("id64_max", idFeat(0xFFFFFFFFFFFFFFFFL), cfg().ids()); var ids32 = array(idFeat(103), idFeat(103), idFeat(103), idFeat(103)); write(layer("ids", ids32), cfg().ids()); write(layer("ids_delta", ids32), cfg(DELTA).ids()); write(layer("ids_rle", ids32), cfg(RLE).ids()); write(layer("ids_delta_rle", ids32), cfg(DELTA_RLE).ids()); var ids64 = array( idFeat(9_234_567_890L), idFeat(9_234_567_890L), idFeat(9_234_567_890L), idFeat(9_234_567_890L)); write(layer("ids64", ids64), cfg().ids()); write(layer("ids64_delta", ids64), cfg(DELTA).ids()); write(layer("ids64_rle", ids64), cfg(RLE).ids()); write(layer("ids64_delta_rle", ids64), cfg(DELTA_RLE).ids()); var optIds = array(idFeat(100), idFeat(101), idFeat(), idFeat(105), idFeat(106)); write(layer("ids_opt", optIds), cfg().ids()); write(layer("ids_opt_delta", optIds), cfg(DELTA).ids()); var optIds64 = array(idFeat(), idFeat(9_234_567_890L), idFeat(101), idFeat(105), idFeat(106)); write(layer("ids64_opt", optIds64), cfg().ids()); write(layer("ids64_opt_delta", optIds64), cfg(DELTA).ids()); // Java does not generate this as an ID64 if none of them are above the U32::Max threshold // FIXME: serialises as i64 // var ids64MinMax = array(idFeat(0L), idFeat(0xFFFFFFFFFFFFFFFFL), idFeat(0L), // idFeat(0xFFFFFFFFFFFFFFFFL)); // write(layer("ids64_minmax", ids64MinMax), cfg().ids()); // write(layer("ids64_minmax_delta", ids64MinMax), cfg(DELTA).ids()); } @SuppressWarnings("cast") private static void generateProperties() throws IOException { write("prop_empty_name", feat(p0, prop("", true)), cfg()); write("prop_special_name", feat(p0, prop("hello\u0000 world\n", true)), cfg()); write("prop_bool", feat(p0, prop("val", true)), cfg()); write("prop_bool_false", feat(p0, prop("val", false)), cfg()); write(layer("prop_bool_true_null", feat(p0, prop("val", true)), feat(p0)), cfg()); write(layer("prop_bool_null_true", feat(p0), feat(p0, prop("val", true))), cfg()); write(layer("prop_bool_false_null", feat(p0, prop("val", false)), feat(p0)), cfg()); write(layer("prop_bool_null_false", feat(p0), feat(p0, prop("val", false))), cfg()); // FIXME: needs support in the Java decoder + encoder // write("prop_i8", feat(p0, prop("val", (byte) 42)), cfg()); // write("prop_i8_neg", feat(p0, prop("val", (byte) -42)), cfg()); // write("prop_i8_min", feat(p0, prop("val", Byte.MIN_VALUE)), cfg()); // write("prop_i8_max", feat(p0, prop("val", Byte.MAX_VALUE)), cfg()); // write(layer("prop_i8_val_null", feat(p0, prop("val", (byte) 42)), feat(p0)), cfg()); // write(layer("prop_i8_null_val", feat(p0), feat(p0, prop("val", (byte) 42))), cfg()); // write("prop_u8", feat(p0, prop("tinynum", U8.of(100))), cfg()); // write("prop_u8_min", feat(p0, prop("tinynum", U8.of(0))), cfg()); // write("prop_u8_max", feat(p0, prop("tinynum", U8.of(255))), cfg()); // write(layer("prop_u8_val_null", feat(p0, prop("val", U8.of(100))), feat(p0)), cfg()); // write(layer("prop_u8_null_val", feat(p0), feat(p0, prop("val", U8.of(100)))), cfg()); // write("prop_i16", feat(p0, prop("val", (short) 42)), cfg()); // write("prop_i16_neg", feat(p0, prop("val", (short) -42)), cfg()); // write("prop_i16_min", feat(p0, prop("val", Short.MIN_VALUE)), cfg()); // write("prop_i16_max", feat(p0, prop("val", Short.MAX_VALUE)), cfg()); // write(layer("prop_i16_val_null", feat(p0, prop("val", (short) 42)), feat(p0)), cfg()); // write(layer("prop_i16_null_val", feat(p0), feat(p0, prop("val", (short) 42))), cfg()); write("prop_i32", feat(p0, prop("val", (int) 42)), cfg()); write("prop_i32_neg", feat(p0, prop("val", (int) -42)), cfg()); write("prop_i32_min", feat(p0, prop("val", Integer.MIN_VALUE)), cfg()); write("prop_i32_max", feat(p0, prop("val", Integer.MAX_VALUE)), cfg()); write(layer("prop_i32_val_null", feat(p0, prop("val", (int) 42)), feat(p0)), cfg()); write(layer("prop_i32_null_val", feat(p0), feat(p0, prop("val", (int) 42))), cfg()); write("prop_u32", feat(p0, prop("val", U32.of(42L))), cfg()); write("prop_u32_min", feat(p0, prop("val", U32.of(0L))), cfg()); write("prop_u32_max", feat(p0, prop("val", U32.of(0xFFFFFFFFL))), cfg()); write(layer("prop_u32_val_null", feat(p0, prop("val", U32.of(42L))), feat(p0)), cfg()); write(layer("prop_u32_null_val", feat(p0), feat(p0, prop("val", U32.of(42L)))), cfg()); long i64_value = 9_876_543_210L; write("prop_i64", feat(p0, prop("val", i64_value)), cfg()); write("prop_i64_neg", feat(p0, prop("val", (long) -9_876_543_210L)), cfg()); write("prop_i64_min", feat(p0, prop("val", Long.MIN_VALUE)), cfg()); write("prop_i64_max", feat(p0, prop("val", Long.MAX_VALUE)), cfg()); write(layer("prop_i64_val_null", feat(p0, prop("val", i64_value)), feat(p0)), cfg()); write(layer("prop_i64_null_val", feat(p0), feat(p0, prop("val", i64_value))), cfg()); U64 u64_value = U64.of(BigInteger.valueOf(1234567890123456789L)); U64 u64_max = U64.of(new BigInteger("18446744073709551615")); write("prop_u64", feat(p0, prop("bignum", u64_value)), cfg()); write("prop_u64_min", feat(p0, prop("bignum", U64.of(BigInteger.ZERO))), cfg()); write("prop_u64_max", feat(p0, prop("bignum", u64_max)), cfg()); write(layer("prop_u64_val_null", feat(p0, prop("val", u64_value)), feat(p0)), cfg()); write(layer("prop_u64_null_val", feat(p0), feat(p0, prop("val", u64_value))), cfg()); write("prop_f32", feat(p0, prop("val", (float) 3.14f)), cfg()); write("prop_f32_neg_inf", feat(p0, prop("val", Float.NEGATIVE_INFINITY)), cfg()); write("prop_f32_min_norm", feat(p0, prop("val", Float.MIN_NORMAL)), cfg()); write("prop_f32_min_val", feat(p0, prop("val", Float.MIN_VALUE)), cfg()); write("prop_f32_neg_zero", feat(p0, prop("val", (float) -0.0f)), cfg()); write("prop_f32_zero", feat(p0, prop("val", (float) 0.0f)), cfg()); write("prop_f32_max", feat(p0, prop("val", Float.MAX_VALUE)), cfg()); write("prop_f32_pos_inf", feat(p0, prop("val", Float.POSITIVE_INFINITY)), cfg()); write("prop_f32_nan", feat(p0, prop("val", Float.NaN)), cfg()); write(layer("prop_f32_val_null", feat(p0, prop("val", 3.14f)), feat(p0)), cfg()); write(layer("prop_f32_null_val", feat(p0), feat(p0, prop("val", 3.14f))), cfg()); write("prop_f64", feat(p0, prop("val", (Double) Math.PI)), cfg()); write("prop_f64_neg_inf", feat(p0, prop("val", Double.NEGATIVE_INFINITY)), cfg()); write("prop_f64_min_norm", feat(p0, prop("val", Double.MIN_NORMAL)), cfg()); write("prop_f64_min_val", feat(p0, prop("val", Double.MIN_VALUE)), cfg()); write("prop_f64_neg_zero", feat(p0, prop("val", (double) -0.0)), cfg()); write("prop_f64_zero", feat(p0, prop("val", (double) 0.0)), cfg()); write("prop_f64_max", feat(p0, prop("val", Double.MAX_VALUE)), cfg()); write("prop_f64_pos_inf", feat(p0, prop("val", Double.POSITIVE_INFINITY)), cfg()); write("prop_f64_nan", feat(p0, prop("val", Double.NaN)), cfg()); write(layer("prop_f64_val_null", feat(p0, prop("val", (Double) Math.PI)), feat(p0)), cfg()); write(layer("prop_f64_null_val", feat(p0), feat(p0, prop("val", (Double) Math.PI))), cfg()); write("prop_str_empty", feat(p0, prop("val", "")), cfg()); write("prop_str_ascii", feat(p0, prop("val", "42")), cfg()); write("prop_str_escape", feat(p0, prop("val", "Line1\n\t\"quoted\"\\path")), cfg()); write("prop_str_unicode", feat(p0, prop("val", "München 📍 cafe\u0301")), cfg()); write("prop_str_special", feat(p0, prop("val", "hello\u0000 world\n")), cfg()); write(layer("prop_str_val_null", feat(p0, prop("val", "42")), feat(p0)), cfg()); write(layer("prop_str_null_val", feat(p0), feat(p0, prop("val", "42"))), cfg()); write(layer("prop_str_val_empty", feat(p0, prop("val", "")), feat(p0)), cfg()); write(layer("prop_str_empty_val", feat(p0), feat(p0, prop("val", ""))), cfg()); // Multiple properties - single feature demonstrating multiple property types write( "props_mixed", feat( p0, props( kv("name", "Test Point"), kv("active", true), // FIXME: needs support in the Java decoder + encoder // kv("tiny-count", (byte) 42), // FIXME: needs support in the decoder + encoder // kv("tiny", U8.of(100)), kv("count", 42), kv("medium", U32.of(100)), kv("bignum", 42L), // FIXME: this is encoded as i32 kv("biggest", U64.of(BigInteger.ZERO)), kv("temp", 25.5f), kv("precision", 0.123456789))), cfg()); // FIXME: needs support in the decoder + encoder // var feat_u8s = // array( // feat(p1, prop("val", U8.of(100))), // feat(p2, prop("val", U8.of(100))), // feat(p3, prop("val", U8.of(100))), // feat(p4, prop("val", U8.of(100)))); // write(layer("props_u8", feat_u8s), cfg()); // write(layer("props_u8_delta", feat_u8s), cfg(DELTA)); // write(layer("props_u8_rle", feat_u8s), cfg(RLE)); // write(layer("props_u8_delta_rle", feat_u8s), cfg(DELTA_RLE)); var feat_ints = array( feat(p0, prop("val", 42)), feat(p1, prop("val", 42)), feat(p2, prop("val", 42)), feat(p3, prop("val", 42))); write(layer("props_i32", feat_ints), cfg()); write(layer("props_i32_delta", feat_ints), cfg(DELTA)); write(layer("props_i32_rle", feat_ints), cfg(RLE)); write(layer("props_i32_delta_rle", feat_ints), cfg(DELTA_RLE)); var feat_u32s = array( feat(p0, prop("val", U32.of(9_000))), feat(p1, prop("val", U32.of(9_000))), feat(p2, prop("val", U32.of(9_000))), feat(p3, prop("val", U32.of(9_000)))); write(layer("props_u32", feat_u32s), cfg()); write(layer("props_u32_delta", feat_u32s), cfg(DELTA)); write(layer("props_u32_rle", feat_u32s), cfg(RLE)); write(layer("props_u32_delta_rle", feat_u32s), cfg(DELTA_RLE)); // The actual frame size is 256, so cover multiples of its half for (var multiplier : new int[] {1, 2, 3, 4}) { for (var offset : new int[] {-1, 0, 1}) { var features = new Feature[128 * multiplier + offset]; for (var i = 0; i < features.length; i++) { // Sequence 0,1,2, 0,1,2, 0,1,2, 0,1,2, ... features[i] = feat(p0, prop("val", U32.of(i % 3))); } write(layer("props_u32_fpf_" + features.length, features), cfg().fastPFOR()); } } var feat_u64s = array( feat(p0, prop("val", U64.of(BigInteger.valueOf(9_000L)))), feat(p1, prop("val", U64.of(BigInteger.valueOf(9_000L)))), feat(p2, prop("val", U64.of(BigInteger.valueOf(9_000L)))), feat(p3, prop("val", U64.of(BigInteger.valueOf(9_000L))))); write(layer("props_u64", feat_u64s), cfg()); write(layer("props_u64_delta", feat_u64s), cfg(DELTA)); write(layer("props_u64_rle", feat_u64s), cfg(RLE)); write(layer("props_u64_delta_rle", feat_u64s), cfg(DELTA_RLE)); // i64 multi-feature: exercises NONE/DELTA/RLE/DELTA_RLE logical techniques for signed 64-bit. // The value must exceed U32::MAX so the encoder uses a 64-bit column. var feat_i64s = array( feat(p0, prop("val", 9_876_543_210L)), feat(p1, prop("val", 9_876_543_210L)), feat(p2, prop("val", 9_876_543_210L)), feat(p3, prop("val", 9_876_543_210L))); write(layer("props_i64", feat_i64s), cfg()); write(layer("props_i64_delta", feat_i64s), cfg(DELTA)); write(layer("props_i64_rle", feat_i64s), cfg(RLE)); write(layer("props_i64_delta_rle", feat_i64s), cfg(DELTA_RLE)); var feat_str = array( feat(p1, prop("val", "residential_zone_north_sector_1")), feat(p2, prop("val", "commercial_zone_south_sector_2")), feat(p3, prop("val", "industrial_zone_east_sector_3")), feat(ph1, prop("val", "park_zone_west_sector_4")), feat(ph2, prop("val", "water_zone_north_sector_5")), feat(ph3, prop("val", "residential_zone_south_sector_6"))); write(layer("props_str", feat_str), cfg()); write(layer("props_str_fsst", feat_str), cfg().fsst()); // 30 because otherwise fsst is skipped var val = "A".repeat(30); // If there are many identical strings in the same column, // an offset directory is used to share them // Because the directory we are indexing into has lengths assosciated with it and the offsets // are indexes, // we cannot do overlap-optimization where one ABBA contains BB -> only ABBA would be in the // dict. // -> ABBABB would need to be in the dict var feat_two_str_eq = array(feat(p1, prop("val", val)), feat(p2, prop("val", val))); write(layer("props_offset_str", feat_two_str_eq), cfg()); write(layer("props_offset_str_fsst", feat_two_str_eq), cfg().fsst()); } /** * Generates FastPFOR tile in 8 variants, with the layer name padded by 0-7 extra bytes, so that * the FPF stream starts at every possible byte offset mod 8. */ private static void generateFpfAlignments() throws IOException { var features = new Feature[128]; for (var i = 0; i < features.length; i++) { features[i] = feat(p0, prop("v", U32.of(i % 3))); } for (var pad = 0; pad < 8; pad++) { write( "fpf_align_" + (pad + 1), List.of(new Layer("a".repeat(pad + 1), Arrays.asList(features), 80)), cfg().fastPFOR()); } } private static void generateSharedDictionaries() throws IOException { // 30 because otherwise fsst is skipped var val = "A".repeat(30); var feat_names = array(feat(p0, props(kv("name:en", val), kv("name:de", val)))); write(layer("props_no_shared_dict", feat_names), cfg()); write(layer("props_shared_dict", feat_names), cfg().sharedDictPrefix("name", ":")); write(layer("props_shared_dict_fsst", feat_names), cfg().sharedDictPrefix("name", ":").fsst()); var feat_one_child = array(feat(p0, props(kv("name:en", val), kv("place", val)))); write( layer("props_shared_dict_one_child", feat_one_child), cfg().sharedDictPrefix("name", ":")); write( layer("props_shared_dict_one_child_fsst", feat_one_child), cfg().sharedDictPrefix("name", ":").fsst()); var feat_distinct_keys_same_value = array(feat(p0, props(kv("a", val), kv("b", val)))); write( layer("props_shared_dict_no_struct_name", feat_distinct_keys_same_value), cfg().sharedDictPrefix("", "")); write( layer("props_shared_dict_no_struct_name_fsst", feat_distinct_keys_same_value), cfg().sharedDictPrefix("", "").fsst()); var feat_names_eq_val_eq = array(feat(p0, props(kv("a", val), kv("a", val)))); write( layer("props_shared_dict_no_child_name", feat_names_eq_val_eq), cfg().sharedDictPrefix("a", "")); write( layer("props_shared_dict_no_child_name_fsst", feat_names_eq_val_eq), cfg().sharedDictPrefix("a", "").fsst()); // Two separate shared dicts with the same "name" prefix, split by explicit column groups var feat_names_4 = array( feat( p0, props( kv("name:de", val), kv("name_en", val), kv("name_fr", val), kv("name:he", val)))); write( layer("props_shared_dict_2_same_prefix", feat_names_4), cfg() .sharedDictColumnGroups( List.of(List.of("name:de", "name_en"), List.of("name_fr", "name:he")))); } } ================================================ FILE: java/mlt-tools/src/main/java/org/maplibre/mlt/tools/SyntheticMltUtil.java ================================================ package org.maplibre.mlt.tools; import static org.maplibre.mlt.converter.ConversionConfig.IntegerEncodingOption.AUTO; import static org.maplibre.mlt.converter.ConversionConfig.IntegerEncodingOption.PLAIN; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.LinearRing; import org.locationtech.jts.geom.MultiLineString; import org.locationtech.jts.geom.MultiPoint; import org.locationtech.jts.geom.MultiPolygon; import org.locationtech.jts.geom.Point; import org.locationtech.jts.geom.Polygon; import org.locationtech.jts.geom.PrecisionModel; import org.maplibre.mlt.converter.ColumnMapping; import org.maplibre.mlt.converter.ColumnMappingConfig; import org.maplibre.mlt.converter.ConversionConfig; import org.maplibre.mlt.converter.FeatureTableOptimizations; import org.maplibre.mlt.converter.MltConverter; import org.maplibre.mlt.data.Feature; import org.maplibre.mlt.data.Layer; import org.maplibre.mlt.data.MLTFeature; import org.maplibre.mlt.data.MapboxVectorTile; import org.maplibre.mlt.decoder.MltDecoder; import org.maplibre.mlt.json.Json; /** Utility helpers for synthetic MLT generation. */ class SyntheticMltUtil { static final Path SYNTHETICS_DIR = Paths.get("../test/synthetic/0x01"); // Using common coordinates everywhere to make sure generated MLT files are very similar, // ensuring we observe difference in encoding rather than geometry variations. // Use SRID 4326 for visualization - all coords are in positive longitude/latitude // range, but in reality the coords are in SRID=0 tile space. // Use coordinates X in 0..180, Y in 0..85 space to match lon/lat ranges and help QGIS vis. // Try to keep all values unique to simplify validation and debugging. // Use tiny tile extent 80 for most geometry tests to focus on encoding correctness. static final GeometryFactory gf = new GeometryFactory(new PrecisionModel(), 4326); static final Coordinate c0 = c(13, 42); // triangle 1, clockwise winding, X ends in 1, Y ends in 2 static final Coordinate c1 = c(11, 52); static final Coordinate c2 = c(71, 72); static final Coordinate c3 = c(61, 22); // triangle 2, clockwise winding, X ends in 3, Y ends in 4 static final Coordinate c21 = c(23, 34); static final Coordinate c22 = c(73, 4); static final Coordinate c23 = c(13, 24); // hole in triangle 1 with counter-clockwise winding static final Coordinate h1 = c(65, 66); static final Coordinate h2 = c(35, 56); static final Coordinate h3 = c(55, 36); static final Point p0 = gf.createPoint(c0); static final Point p1 = gf.createPoint(c1); static final Point p2 = gf.createPoint(c2); static final Point p3 = gf.createPoint(c3); // holes as points with same coordinates as the hole vertices static final Point ph1 = gf.createPoint(h1); static final Point ph2 = gf.createPoint(h2); static final Point ph3 = gf.createPoint(h3); static final LineString line1 = line(c1, c2, c3); static final LineString line2 = line(c21, c22, c23); static final Polygon poly1 = poly(c1, c2, c3, c1); static final Polygon poly2 = poly(c21, c22, c23, c21); static final Polygon poly1h = poly(ring(c1, c2, c3, c1), ring(h1, h2, h3, h1)); // 4x4 complete Morton block: 16 points decoded from Z-order (even bits → x, odd bits → y). // Shared by line_morton and poly_morton so both test the same coordinate pattern. static final Coordinate[] mortonCurve = buildMortonCurve(16, 8, 4); /** Builder subclass with no-argument shorthand methods for common flags. */ static class Cfg extends ConversionConfig.ConfigBuilder { public Cfg ids() { super.includeIds(true); return this; } public Cfg fastPFOR() { super.useFastPFOR(true); return this; } public Cfg fsst() { super.useFSST(true); return this; } public Cfg geomEnc(ConversionConfig.IntegerEncodingOption encoding) { super.geometryEncodingOption(encoding); return this; } public Cfg coercePropValues() { super.typeMismatchPolicy(ConversionConfig.TypeMismatchPolicy.COERCE); return this; } public Cfg tessellate() { super.preTessellatePolygons(true); return this; } public Cfg morton() { super.useMortonEncoding(true); return this; } public Cfg filterInvert() { super.layerFilterInvert(true); return this; } /** * Enable shared dictionary encoding for properties with names starting with the given prefix * string. */ public Cfg sharedDictPrefix(String prefix, String delimiter) { var mapping = new ColumnMapping(prefix, delimiter, true); var layerOpt = new FeatureTableOptimizations(false, false, List.of(mapping)); var optimizationsMap = new HashMap(); optimizationsMap.put("layer1", layerOpt); super.optimizations(optimizationsMap); return this; } /** * Enable shared dictionary encoding using explicit groups of column names. Each group becomes * its own shared dictionary. */ public Cfg sharedDictColumnGroups(List> columnGroups) { var mappings = columnGroups.stream().map(cols -> new ColumnMapping(cols, true)).toList(); var layerOpt = new FeatureTableOptimizations(false, false, mappings); var optimizationsMap = new HashMap(); optimizationsMap.put("layer1", layerOpt); this.optimizations(optimizationsMap); return this; } } static Cfg cfg() { return cfg(PLAIN); } static Cfg cfg(ConversionConfig.IntegerEncodingOption encoding) { var c = new Cfg(); c.includeIds(false); c.useFastPFOR(false); c.useFSST(false); c.typeMismatchPolicy(ConversionConfig.TypeMismatchPolicy.FAIL); // c.optimizations(null); // Map c.preTessellatePolygons(false); c.useMortonEncoding(false); // ALWAYS use outlines for synthetic tests. When tessellation is enabled, a polygon outline // also needs to be stored in the tile, or else only triangles would be stored and the // Java decoder (and other decoders) do not currently support triangle meshes without // corresponding outline polygons. Using "ALL" here ensures outlines are present for both // tessellated and non-tessellated cases in all synthetic test tiles. c.outlineFeatureTableNames(List.of("ALL")); // c.layerFilterPattern(null); // Pattern c.layerFilterInvert(false); c.integerEncodingOption(encoding); c.geometryEncodingOption(AUTO); return c; } @SafeVarargs @SuppressWarnings("varargs") static T[] array(T... elements) { return elements; } static Coordinate c(int x, int y) { return new Coordinate(x, y); } /** De-interleave Z-order index bits into x (even bits) and y (odd bits) coordinates. */ static Coordinate[] buildMortonCurve(int numPoints, int scale, int mortonBits) { var curve = new Coordinate[numPoints]; for (var i = 0; i < numPoints; i++) { var x = 0; var y = 0; for (var b = 0; b < mortonBits; b++) { x |= ((i >> (2 * b)) & 1) << b; y |= ((i >> (2 * b + 1)) & 1) << b; } curve[i] = c(x * scale, y * scale); } return curve; } static LineString line(Coordinate... coords) { return gf.createLineString(coords); } static LinearRing ring(Coordinate... coords) { return gf.createLinearRing(coords); } static Polygon poly(Coordinate... coords) { return gf.createPolygon(coords); } static Polygon poly(LinearRing shell, LinearRing... holes) { return gf.createPolygon(shell, holes); } static MultiPoint multi(Coordinate... coords) { var pts = Arrays.stream(coords).map(t -> gf.createPoint(t)).toArray(Point[]::new); return gf.createMultiPoint(pts); } static MultiPolygon multi(Polygon... polys) { return gf.createMultiPolygon(polys); } static MultiLineString multi(LineString... lines) { return gf.createMultiLineString(lines); } record KeyVal(String key, Object value) {} static KeyVal kv(String key, Object value) { return new KeyVal(key, value); } static Map prop(String key, Object value) { return Map.of(key, value); } static Map props(KeyVal... keyValues) { var map = new java.util.HashMap(); for (var kv : keyValues) { map.put(kv.key, kv.value); } return map; } static Feature feat(Geometry geom) { return MLTFeature.builder().index(0).geometry(geom).build(); } static MLTFeature feat(Geometry geom, Map props) { return MLTFeature.builder().index(0).geometry(geom).rawProperties(props).build(); } /** for testing IDs - always use the same geometry */ static Feature idFeat(long id) { return MLTFeature.builder().index(0).id(id).geometry(p0).build(); } /** for testing IDs - simulate missing ID */ static Feature idFeat() { return feat(p0); } static Layer layer(String name, Feature... features) { return new Layer(name, Arrays.asList(features), 80); } static Layer layer(String name, int extent, Feature... features) { return new Layer(name, Arrays.asList(features), extent); } static void write(String name, Feature feat, ConversionConfig.ConfigBuilder cfg) throws IOException { write(layer(name, feat), cfg); } static void write(Layer layer, ConversionConfig.ConfigBuilder cfg) throws IOException { // layer names should be identical to reduce variability in generated MLT files // and ensure we observe differences in encoding rather than layer name variations write(layer.name(), List.of(new Layer("layer1", layer.features(), layer.tileExtent())), cfg); } static void write(String fileName, List layers, ConversionConfig.ConfigBuilder cfg) throws IOException { try { System.out.println("Generating: " + fileName); var config = cfg.build(); var tile = new MapboxVectorTile(layers); final var columnMappings = buildColumnMappings(config); var metadata = MltConverter.createTilesetMetadata(tile, columnMappings, config.includeIds()); var mlt = MltConverter.encode(tile, metadata, config, null); Files.write(SYNTHETICS_DIR.resolve(fileName + ".mlt"), mlt, StandardOpenOption.CREATE_NEW); final String json = Json.toGeoJson(MltDecoder.decodeMlTile(mlt), true) + "\n"; Files.writeString( SYNTHETICS_DIR.resolve(fileName + ".json"), json, StandardOpenOption.CREATE_NEW); } catch (Exception e) { throw new IOException("Error writing MLT file " + fileName, e); } } private static ColumnMappingConfig buildColumnMappings(ConversionConfig config) { final var columnMappings = new ColumnMappingConfig(); if (config.optimizations() != null && !config.optimizations().isEmpty()) { var allColumnMappings = config.optimizations().values().stream() .flatMap(opt -> opt.columnMappings().stream()) .toList(); if (!allColumnMappings.isEmpty()) { columnMappings.put(Pattern.compile(".*"), allColumnMappings); } } return columnMappings; } } ================================================ FILE: java/mod.just ================================================ ci_mode := if env('CI', '') != '' {'1'} else {''} just := quote(just_executable()) _default: (just '--list' 'java') [private] just *args: {{just}} {{args}} # Quick compile check check: ./gradlew check # Run all CI steps: lint, test, cli tests, coverage, synthetic MLT check ci-test: env-info lint test test-cli test-publish ci-check-synthetic-mlts {{just}} assert-git-is-clean # Verify synthetic MLT files are up to date ci-check-synthetic-mlts: {{just}} assert-git-is-clean {{just}} java::generate-synthetic-mlts {{just}} assert-git-is-clean @echo "Synthetic MLT check complete." # Print environment info env-info: @echo "Running {{if ci_mode == '1' {'in CI mode'} else {'in dev mode'} }} on {{os()}} / {{arch()}}" ./gradlew --version # Reformat code fmt: ./gradlew spotlessApply # Clean build artifacts clean: ./gradlew clean # Build Java encoder and generate .mlt files for all fixtures generate-expected-mlt: (just 'cargo-install' 'fd' 'fd-find') ./gradlew cli fd . ../test/fixtures --no-ignore --extension pbf --extension mvt -x {{just}} java::_generate-one-expected-mlt # Build Java encoder and run it to generate snippets for all .pbf files gen-snippets: (just 'cargo-install' 'fd' 'fd-find') #!/usr/bin/env bash set -euo pipefail ./gradlew cli fd . ../test/fixtures -e pbf -x java -jar mlt-cli/build/libs/encode.jar --mvt {} \ --mlt "$(tmp='{.}'; echo ${tmp/\/fixtures\///expected/}).mvt" \ --rawstreams --tessellate --outlines ALL || echo "### Some snippets failed to generate ###" # Build Java encoder and run it to generate test files gen-tests: (just 'cargo-install' 'fd' 'fd-find') #!/usr/bin/env bash set -euo pipefail ./gradlew cli fd . ../test/fixtures -e pbf -x bash -c 'java -jar mlt-cli/build/libs/encode.jar --mvt {} \ --mlt "$(tmp={.}; echo ${tmp/fixtures/expected/tag0x01/}).mlt" \ --outlines "\*" --tessellate --coerce-mismatch --verbose || echo "### Some snippets failed to generate ###"' # Generate synthetic .mlt files and ensure there are no duplicates generate-synthetic-mlts: #!/usr/bin/env bash set -euo pipefail rm -rf ../test/synthetic/0x01 ./gradlew :mlt-tools:generateSyntheticMlt {{just}} _assert-all-mlt-files-different # Run linting (format check) lint: ./gradlew spotlessJavaCheck # Run tests test: ./gradlew test # Run Java cli tests test-cli: #!/usr/bin/env bash set -euo pipefail JAVA="java -Dcom.google.protobuf.use_unsafe_pre22_gencode" ENCODE="$JAVA -jar ./mlt-cli/build/libs/encode.jar" DECODE="$JAVA -jar ./mlt-cli/build/libs/decode.jar" ./gradlew cli $ENCODE --mvt ../test/fixtures/omt/10_530_682.mvt --mlt output/varint.mlt --decode $ENCODE --mvt ../test/fixtures/omt/10_530_682.mvt --enable-fastpfor --enable-fsst --mlt output/advanced.mlt $DECODE --mlt output/advanced.mlt $ENCODE --mbtiles ../test/fixtures/omt.max1.mbtiles --outlines ALL --colmap-delim '[]name/[:_]/' --tessellate --sort-ids --coerce-mismatch --verbose warn --parallel $ENCODE --pmtiles ../test/fixtures/omt-planet-20260112.mvt.max1.pmtiles --outlines ALL --colmap-delim '[]name/[:_]/' --tessellate --sort-ids --coerce-mismatch --verbose warn --parallel # Run tests with coverage test-coverage: ./gradlew jacocoTestReport # Test Maven local publishing test-publish: ./gradlew publishMavenPublicationToMavenLocal # Run benchmark tests bench: ./gradlew test -Dbenchmark.iterations=200 -PincludeTags=benchmark ./gradlew jmh _generate-one-expected-mlt file: java \ -Dcom.google.protobuf.use_unsafe_pre22_gencode \ -jar mlt-cli/build/libs/encode.jar \ --mvt {{quote(file)}} \ --mlt {{quote(replace(without_extension(file) + '.mlt', '/fixtures/', '/expected/tag0x01/'))}} \ --outlines ALL \ --colmap-delim '[.*]name/[:_]/' \ --enable-fsst \ --tessellate \ --coerce-mismatch \ --verbose ================================================ FILE: java/resources/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.5) project(fsst) find_package(Threads REQUIRED) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_VERBOSE_MAKEFILE ON) if(APPLE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -arch x86_64 -arch arm64") endif(APPLE) if (LINUX) include(CheckCXXCompilerFlag) check_cxx_compiler_flag("-march=native" COMPILER_SUPPORTS_MARCH_NATIVE) if(COMPILER_SUPPORTS_MARCH_NATIVE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native") endif() endif(LINUX) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() if(MSVC) set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O3 /DNDEBUG") else() set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -DNDEBUG") endif() find_package(JNI) if (JNI_FOUND) message (STATUS "JNI_INCLUDE_DIRS=${JNI_INCLUDE_DIRS}") message (STATUS "JNI_LIBRARIES=${JNI_LIBRARIES}") endif() include(ExternalProject) ExternalProject_Add(FsstDownload GIT_REPOSITORY https://github.com/springmeyer/fsst GIT_TAG bd766bc # https://github.com/springmeyer/fsst/tree/dane/cross-platform-build-fixes INSTALL_COMMAND "" PREFIX ${CMAKE_CURRENT_BINARY_DIR}/libfsst-prefix CMAKE_ARGS "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}" ) SET(LIBFSST_LIB_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/libfsst-prefix/src/FsstDownload-build) SET(LIBFSST_INC_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/libfsst-prefix/src/FsstDownload) link_directories(${LIBFSST_LIB_DIRECTORY}) add_library(libfsst STATIC IMPORTED) # TODO: find a way to avoid having to be explicit about library name differences between platforms if(MSVC) set_target_properties(libfsst PROPERTIES IMPORTED_LOCATION ${LIBFSST_LIB_DIRECTORY}/Release/fsst.lib) else() set_target_properties(libfsst PROPERTIES IMPORTED_LOCATION ${LIBFSST_LIB_DIRECTORY}/libfsst.a) endif() add_library(FsstWrapper MODULE ${CMAKE_CURRENT_SOURCE_DIR}/FsstWrapper.cpp) add_dependencies(FsstWrapper FsstDownload) set_target_properties(FsstWrapper PROPERTIES OUTPUT_NAME "${output_name}" POSITION_INDEPENDENT_CODE ON SUFFIX ".so" PREFIX "" ) target_include_directories(FsstWrapper PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${JNI_INCLUDE_DIRS} ${LIBFSST_INC_DIRECTORY}) target_link_libraries(FsstWrapper libfsst) ================================================ FILE: java/resources/FsstWrapper.cpp ================================================ #include #include #include #include #include #include struct SymbolTableStruct { unsigned char symbolLengths[255]; unsigned long long symbols[255]; int nSymbols; std::vector compressedData; }; SymbolTableStruct fsstCompress(std::vector inputBytes) { unsigned long n = 1; auto** srcBuf = (uint8_t**)calloc(n, sizeof(uint8_t*)); auto** dstBuf = (uint8_t**)calloc(n, sizeof(uint8_t*)); auto* srcLen = (size_t*)calloc(n, sizeof(size_t)); auto* dstLen = (size_t*)calloc(n, sizeof(size_t)); srcBuf[0] = inputBytes.data(); srcLen[0] = inputBytes.size(); uint64_t before_size = inputBytes.size(); unsigned char serialized_encoder_buf[FSST_MAXHEADER]; fsst_encoder_t* encoder = fsst_create(n, srcLen, const_cast(srcBuf), 0); fsst_export(encoder, serialized_encoder_buf); // the first 8 bytes of serialized_encoder_buf is where the version field is stored uint64_t version; memcpy(&version, serialized_encoder_buf, 8); // nSymbols is stored in the second byte from the right of version uint32_t nSymbols = (version >> 8) & 0xFF; uint8_t lenHisto[8]; for (uint32_t i = 0; i < 8; i++) lenHisto[i] = serialized_encoder_buf[9 + i]; unsigned long output_buffer_size = 7 + 4 * before_size; // 1024 * 1024 * 1024 auto output_buffer = (uint8_t*)calloc(output_buffer_size, sizeof(uint8_t)); fsst_compress( encoder, n, srcLen, const_cast(srcBuf), output_buffer_size, output_buffer, dstLen, dstBuf); size_t compressedDataLength = *dstLen; fsst_decoder_t decoder; fsst_import(&decoder, serialized_encoder_buf); // Pack symbolTableStruct with relevant data SymbolTableStruct symbolTableStruct{}; symbolTableStruct.nSymbols = nSymbols; memcpy(symbolTableStruct.symbolLengths, decoder.len, sizeof(decoder.len)); memcpy(symbolTableStruct.symbols, decoder.symbol, sizeof(decoder.symbol)); for (size_t i = 0; i < n; ++i) { for (size_t j = 0; j < compressedDataLength; ++j) { symbolTableStruct.compressedData.push_back(dstBuf[i][j]); } } fsst_destroy(encoder); return symbolTableStruct; } JNIEXPORT jobject JNICALL Java_com_mlt_converter_encodings_fsst_FsstJni_compress(JNIEnv* env, jclass cls, jbyteArray inputBytes) { jbyte* bytes = env->GetByteArrayElements(inputBytes, NULL); jsize length = env->GetArrayLength(inputBytes); std::vector byteVector(bytes, bytes + length); // Don't forget to release the memory as it's a direct pointer to the java array. env->ReleaseByteArrayElements(inputBytes, bytes, 0); SymbolTableStruct result = fsstCompress(byteVector); // Convert symbolLengths array and symbols array jsize nSymbols = (jint)result.nSymbols; jintArray symbolLengthsArray = env->NewIntArray(nSymbols); jint* tempIntData = new jint[nSymbols]; int totalSymbolLengths = 0; for (int i = 0; i < nSymbols; i++) { totalSymbolLengths += result.symbolLengths[i]; } jbyteArray symbolsArray = env->NewByteArray(totalSymbolLengths); int offset = 0; for (int i = 0; i < nSymbols; i++) { tempIntData[i] = (jint)result.symbolLengths[i]; env->SetByteArrayRegion( symbolsArray, offset, tempIntData[i], reinterpret_cast(&result.symbols[i])); offset += tempIntData[i]; } env->SetIntArrayRegion(symbolLengthsArray, 0, nSymbols, tempIntData); delete[] tempIntData; // Convert compressedData to a Java byte array auto compressedDataLength = result.compressedData.size(); jbyteArray compressedData = env->NewByteArray(compressedDataLength); env->SetByteArrayRegion(compressedData, 0, compressedDataLength, (jbyte*)&result.compressedData[0]); // Create the Java SymbolTable object jclass symbolTableClass = env->FindClass("com/mlt/converter/encodings/fsst/SymbolTable"); jmethodID symbolTableCtor = env->GetMethodID(symbolTableClass, "", "([B[I[BI)V"); jobject javaSymbolTable = env->NewObject( symbolTableClass, symbolTableCtor, symbolsArray, symbolLengthsArray, compressedData, length); return javaSymbolTable; } ================================================ FILE: java/resources/FsstWrapper.h ================================================ /* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class FsstWrapper */ #ifndef _Included_FsstWrapper #define _Included_FsstWrapper #ifdef __cplusplus extern "C" { #endif /* * Class: FsstWrapper * Method: compress * Signature: ([B)LSymbolTable; */ JNIEXPORT jobject JNICALL Java_com_mlt_converter_encodings_fsst_FsstJni_compress(JNIEnv*, jclass, jbyteArray); #ifdef __cplusplus } #endif #endif ================================================ FILE: java/resources/compile ================================================ #!/bin/bash set -euo pipefail if [[ -f build/FsstWrapper.so ]]; then echo "FsstWrapper.so exists, skipping compilation" echo " Remove ./build/FsstWrapper.so to recompile" echo " Remove ./build to reconfigure & compiled" else echo "FsstWrapper.so does not exist, building now" mkdir -p build cd build cmake ../resources cmake --build . --config Release fi echo "Compilation successful" ================================================ FILE: java/resources/compile-windows.bat ================================================ @echo off IF EXIST build\Release\FsstWrapper.so ( echo "FsstWrapper.so exists, skipping compilation" echo " Remove ./build/Release/FsstWrapper.so to recompile" echo " Remove ./build to reconfigure & compiled" ) ELSE ( echo "FsstWrapper.so does not exist, building now" mkdir build cd build cmake ../Resources cmake --build . --config Release ) IF %ERRORLEVEL% EQU 0 ( echo "Compilation successful" exit /b 0 ) ELSE ( echo "Compilation failed" exit /b 1 ) ================================================ FILE: java/settings.gradle ================================================ plugins { id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' } rootProject.name = 'maplibre-tiles' include 'mlt-core' include 'mlt-cli' include 'mlt-tools' ================================================ FILE: java/tessellation/index.mjs ================================================ import bodyParser from "body-parser"; import earcut from "earcut"; import express from "express"; /* Quick and dirty workaround to us the js version of earcut for pre-tessellating polygons in the java converter * since the Java version of earcut seems to have minor issue */ const app = express(); app.use(bodyParser.json({ limit: "50mb" })); app.use(bodyParser.urlencoded({ limit: "50mb", extended: true })); app.use(express.json()); app.post("/tessellate", (req, res) => { const data = req.body; const { vertices, holes } = data; const indices = earcut(vertices, holes, 2); const response = { indices }; res.json(response); }); const port = 3000; app.listen(port, () => { console.log(`Server is running at http://localhost:${port}`); }); ================================================ FILE: java/tessellation/package.json ================================================ { "name": "tessellation-server", "version": "0.0.1-alpha.5", "scripts": { "serve": "node index.mjs" }, "dependencies": { "body-parser": "^1.20.3", "earcut": "^3.0.1", "express": "^4.21.1" } } ================================================ FILE: justfile ================================================ #!/usr/bin/env just --justfile mod cpp mod java mod rust mod ts just := quote(just_executable()) ci_mode := if env('CI', '') != '' {'1'} else {''} # By default, show the list of all available commands @_default: {{just}} --list --list-submodules bench: {{just}} rust::bench {{just}} java::bench {{just}} ts::bench # Run integration tests, and override what we expect the output to be with the actual output bless: _clean-int-test _test-run-int {{just}} rust::bless rm -rf test/expected && mv test/output test/expected # Delete all build files for multiple languages clean: {{just}} rust::clean {{just}} java::clean {{just}} ts::clean {{just}} cpp::clean # Run all formatting in every language fmt: {{just}} rust::fmt {{just}} java::fmt {{just}} ts::fmt {{just}} cpp::fmt # Run linting in every language. Run `just fmt` to fix formatting issues. lint: {{just}} rust::lint {{just}} java::lint {{just}} ts::lint {{just}} cpp::lint # Run all tests in every language, including integration tests test: test-int {{just}} rust::test {{just}} java::test {{just}} ts::test {{just}} cpp::test # Run integration tests, ensuring that the output matches the expected output test-int: _clean-int-test _test-run-int (_diff-dirs "test/output" "test/expected") docs: docker run --rm -it -p 8000:8000 -v ${PWD}:/docs zensical/zensical:latest docs-build: docker run --rm -v ${PWD}:/docs zensical/zensical:latest build # Extract version from a tag by removing language prefix and 'v' prefix ci-extract-version language tag: @echo "{{replace(replace(tag, language + '-', ''), 'v', '')}}" # Run the mlt CLI tool with the given arguments from current dir. [no-cd] [positional-arguments] # avoids shell expansions mlt *args: cargo run --manifest-path {{join(justfile_directory(), 'rust', 'Cargo.toml')}} --package mlt -- "$@" # Ensure a command is available assert-cmd command: #!/usr/bin/env bash set -euo pipefail if ! type {{command}} > /dev/null; then echo "Command '{{command}}' could not be found. Please make sure it has been installed on your computer." exit 1 fi # Install a Cargo tool if missing (uses cargo-binstall when available) cargo-install $COMMAND $INSTALL_CMD='' *args='': #!/usr/bin/env bash set -euo pipefail binstall_args="{{ if env('CI', '') != '' {'--no-confirm --no-track --disable-telemetry'} else {''} }}" if ! command -v $COMMAND > /dev/null; then if ! command -v cargo-binstall > /dev/null; then echo "$COMMAND could not be found. Installing it with cargo install ${INSTALL_CMD:-$COMMAND} --locked {{args}}" cargo install ${INSTALL_CMD:-$COMMAND} --locked {{args}} else echo "$COMMAND could not be found. Installing it with cargo binstall ${INSTALL_CMD:-$COMMAND} $binstall_args --locked {{args}}" cargo binstall ${INSTALL_CMD:-$COMMAND} $binstall_args --locked {{args}} fi fi # Install the pmtiles CLI and Python pmtiles library (needed by download-benchmark-tiles) install-pmtiles: #!/usr/bin/env bash set -euo pipefail if ! command -v pmtiles > /dev/null; then OS=$(uname -s | tr '[:upper:]' '[:lower:]') ARCH=$(uname -m) case "$OS-$ARCH" in linux-x86_64) SUFFIX="Linux_x86_64" ;; linux-aarch64) SUFFIX="Linux_arm64" ;; darwin-x86_64) SUFFIX="Darwin_x86_64" ;; darwin-arm64) SUFFIX="Darwin_arm64" ;; *) echo "Unsupported platform: $OS-$ARCH"; exit 1 ;; esac AUTH=() if [ -n "${GITHUB_TOKEN:-}" ]; then AUTH=(-H "Authorization: Bearer ${GITHUB_TOKEN}") fi ASSET_URL=$(curl -sSf "${AUTH[@]}" https://api.github.com/repos/protomaps/go-pmtiles/releases/latest | \ jq -r ".assets[] | select(.name | test(\"${SUFFIX}\")) | .browser_download_url") if [ -z "$ASSET_URL" ] || [ "$ASSET_URL" = "null" ]; then echo "Could not find pmtiles release for $SUFFIX"; exit 1 fi curl -sSfL "$ASSET_URL" | sudo tar -xz -C /usr/local/bin pmtiles fi echo "pmtiles: $(pmtiles version)" pip install --break-system-packages pmtiles 2>/dev/null \ || pip install pmtiles # Make sure the git repo has no uncommitted changes. Fails only if CI envvar is set. assert-git-is-clean: #!/usr/bin/env bash set -euo pipefail if [ -n "$(git status --porcelain --untracked-files=all)" ]; then >&2 echo "::error::git repo is not clean. Make sure compilation and tests artifacts are in the .gitignore, and no repo files are modified." if [[ "{{ci_mode}}" == "1" ]]; then >&2 echo "::group::git status" git status >&2 echo "::endgroup::" >&2 echo "::group::git diff (tracked changes)" git add . --intent-to-add git --no-pager diff >&2 echo "::endgroup::" exit 1 else >&2 echo "git repo is not clean, but not failing because CI mode is not enabled." fi fi _clean-int-test: rm -rf test/output && mkdir -p test/output _diff-dirs OUTPUT_DIR EXPECTED_DIR: #!/usr/bin/env bash set -euo pipefail echo "** Comparing {{OUTPUT_DIR}} with {{EXPECTED_DIR}}..." if ! diff --brief --recursive --new-file {{OUTPUT_DIR}} {{EXPECTED_DIR}}; then echo "** Expected output does not match actual output" echo "** You may want to run 'just bless' to update expected output" exit 1 else echo "** Expected output matches actual output" fi _test-run-int: echo "TODO: Add integration test command, outputting to test/output" echo "fake output by copying expected into output so that the rest of the script works" # TODO: REMOVE THIS, and replace it with a real integration test run cp -r test/expected/* test/output # Ensure there are no duplicate synthetic MLT files by comparing their hashes. _assert-all-mlt-files-different dir='test/synthetic': #!/usr/bin/env bash set -euo pipefail all_hashes=$(find {{quote(dir)}} -name '*.mlt' -exec sha256sum {} \; | sort) duplicates=$(echo "$all_hashes" | awk '{print $1}' | uniq -d) if [ -n "$duplicates" ]; then echo "::error::Duplicate synthetic MLT files found" while IFS= read -r hash; do echo "" echo "$all_hashes" | grep "^$hash " | awk '{print " - " $2}' done <<< "$duplicates" exit 1 fi ================================================ FILE: mkdocs.yml ================================================ site_name: MapLibre Tile Specification site_url: https://www.maplibre.org/maplibre-tile-spec repo_url: https://github.com/maplibre/maplibre-tile-spec site_description: MapLibre Tile Specification edit_uri: edit/main/docs extra_css: - assets/extra.css theme: name: 'material' favicon: https://maplibre.org/favicon.ico logo: assets/logo.svg features: - content.code.copy - search.suggest - navigation.instant - navigation.sections - content.action.edit - toc.integrate palette: - media: "(prefers-color-scheme)" toggle: icon: material/brightness-auto name: Switch to light mode - media: "(prefers-color-scheme: dark)" scheme: slate toggle: icon: material/brightness-4 name: Switch to system preference - media: "(prefers-color-scheme: light)" scheme: default toggle: icon: material/brightness-7 name: Switch to dark mode markdown_extensions: - pymdownx.highlight: anchor_linenums: true line_spans: __span pygments_lang_class: true - pymdownx.inlinehilite - pymdownx.snippets: dedent_subsections: true check_paths: true base_path: docs/snippets - pymdownx.superfences: custom_fences: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format - pymdownx.escapeall: hardbreak: True nbsp: True - admonition - pymdownx.details - footnotes - attr_list - md_in_html - toc: permalink: true permalink_title: "Link to this section" baselevel: 1 toc_depth: 6 nav: - Home: index.md - Implementation Status: implementation-status.md - Encoding Algorithms: encodings.md - Specification: specification.md - Geometry Spec: geometry.md extra: social: - icon: fontawesome/brands/mastodon link: https://mastodon.social/@maplibre - icon: fontawesome/brands/x-twitter link: https://twitter.com/maplibre - icon: fontawesome/brands/linkedin link: https://www.linkedin.com/company/maplibre - icon: fontawesome/brands/slack link: https://osmus.slack.com/archives/C01G3D28DAB - icon: fontawesome/brands/github link: https://github.com/maplibre plugins: - search - callouts - social: cards_layout_options: background_color: '#295DAA' - panzoom - add-number: excludes: ['*'] includes: - specification.md validation: omitted_files: warn absolute_links: warn unrecognized_links: warn anchors: warn ================================================ FILE: qgis/.gitignore ================================================ __pycache__/ ================================================ FILE: qgis/README.md ================================================ # QGIS MLT Plugin A QGIS plugin to open **MapLibre Tile (MLT)** files, powered by the Rust `mlt` parser exposed to Python via [PyO3](https://pyo3.rs). ## Architecture ``` ┌─────────────────────┐ │ QGIS Plugin │ Python - registers menu action, creates memory layers │ (qgis/mlt_plugin) │ └────────┬────────────┘ │ import ┌────────▼────────────┐ │ maplibre-tiles │ Rust -> Python bridge (PyO3 + maturin) │ (rust/mlt-py) │ └────────┬────────────┘ │ depends on ┌────────▼────────────┐ │ mlt-core │ Rust - zero-copy MLT binary parser │ (rust/mlt-core) │ └─────────────────────┘ ``` ## Prerequisites - **QGIS 3.22+** (tested with 3.34) - **Rust toolchain** (1.87+) — install via [rustup](https://rustup.rs) - **maturin** — PyO3 build tool (`pip install maturin`) - **Python 3.9+** (must match the Python that QGIS uses) ## Installation ### Step 1: Find QGIS's Python interpreter The `mlt` native module must be built for the same Python that QGIS uses. ```bash # Linux (system Python, usually the same one QGIS links to) /usr/bin/python3 -c "import qgis.core; print('QGIS bindings OK')" # macOS (QGIS bundles its own Python) /Applications/QGIS.app/Contents/MacOS/bin/python3 --version ``` Use whichever interpreter prints "QGIS bindings OK" in the commands below. ### Step 2: Build and install the native module ```bash cd rust/mlt-py # Option A: build a wheel, then install it pip install maturin # in any environment maturin build --release --interpreter /usr/bin/python3 # Install into user site-packages (visible to QGIS) /usr/bin/python3 -m pip install --user --break-system-packages \ ../../rust/target/wheels/mlt-*.whl # Option B: if QGIS uses a virtualenv / conda, activate it first # conda activate qgis-env # or: source /path/to/venv/bin/activate # maturin develop --release ``` Verify: ```bash /usr/bin/python3 -c "import maplibre_tiles; print(maplibre_tiles.list_layers(open('../../test/synthetic/0x01/polygon.mlt','rb').read()))" # Expected: ['layer1'] ``` ### Step 3: Symlink the plugin into QGIS ```bash # Linux ln -sfn "$(pwd)/../../qgis/mlt_plugin" \ ~/.local/share/QGIS/QGIS3/profiles/default/python/plugins/mlt_plugin # macOS ln -sfn "$(pwd)/../../qgis/mlt_plugin" \ ~/Library/Application\ Support/QGIS/QGIS3/profiles/default/python/plugins/mlt_plugin # Windows (PowerShell, run as Admin) # New-Item -ItemType SymbolicLink ` # -Path "$env:APPDATA\QGIS\QGIS3\profiles\default\python\plugins\mlt_plugin" ` # -Target (Resolve-Path "..\..\qgis\mlt_plugin") ``` ### Step 4: Enable in QGIS 1. Launch QGIS 2. **Plugins → Manage and Install Plugins** 3. Search for **MLT Provider**, check the box to enable it 4. A toolbar icon and menu entry appear under **Vector → MLT Provider → Open MLT File(s)…** ## Usage ### Opening a single tile 1. Click **Open MLT File(s)…** 2. Select one `.mlt` file 3. A dialog appears with auto-detected **z/x/y** coordinates (parsed from the filename, e.g. `14_8297_10749.mlt` → z=14, x=8297, y=10749) 4. Confirm or edit the values, toggle **TMS y-axis** if needed 5. Click **OK** — each MLT layer loads as a QGIS memory layer with real EPSG:3857 coordinates ### Opening multiple tiles at once 1. Click **Open MLT File(s)…** 2. Select multiple `.mlt` files (Ctrl+click / Shift+click) 3. A table dialog shows all files with their auto-detected z/x/y 4. Options: - **TMS y-axis** — checked by default (correct for OpenMapTiles / MBTiles) - **Merge same-named layers** — checked by default; combines features from the same MLT layer across tiles into one QGIS layer for seamless viewing 5. Click **OK** — e.g. selecting a 3×3 tile grid creates unified "building", "transportation", "water" layers containing features from all 9 tiles ### Coordinate conventions | Convention | y=0 is at | Used by | |---|---|---| | **TMS** (default) | South | OpenMapTiles, MBTiles, TileJSON | | **XYZ** | North | OSM raster tile servers | If coordinates look wrong (features in the ocean), try toggling TMS. Click **Skip (raw coords)** to load tile-local integer coordinates without geo-referencing (useful for inspecting raw tile data). ## Troubleshooting **"mlt module not found"** — the native module isn't installed for QGIS's Python. Re-run Step 2 using the correct interpreter. **Features appear in the ocean** — toggle the TMS checkbox, or verify z/x/y values are correct. **Plugin not visible in QGIS** — check the symlink points to the right directory and that the `metadata.txt` file exists inside it. **Build fails with "unsafe_code forbidden"** — the `mlt-py` crate overrides the workspace lint locally; make sure you're building from `rust/mlt-py/`, not the workspace root. ================================================ FILE: qgis/mlt_plugin/__init__.py ================================================ """QGIS plugin entry point for MLT (MapLibre Tile) file support.""" def classFactory(iface): from .plugin import MltPlugin return MltPlugin(iface) ================================================ FILE: qgis/mlt_plugin/loader.py ================================================ """Loads MLT files into QGIS memory layers using mlt.""" from collections import defaultdict from pathlib import Path from typing import Dict, List, Optional, Tuple from qgis.core import ( Qgis, QgsFeature, QgsField, QgsFields, QgsGeometry, QgsMessageLog, QgsProject, QgsVectorLayer, QgsWkbTypes, ) from qgis.PyQt.QtCore import QVariant try: import mlt except ImportError: mlt = None PLUGIN_NAME = "MLT Provider" GEOM_TYPE_MAP = { "Point": QgsWkbTypes.MultiPoint, "LineString": QgsWkbTypes.MultiLineString, "Polygon": QgsWkbTypes.MultiPolygon, "MultiPoint": QgsWkbTypes.MultiPoint, "MultiLineString": QgsWkbTypes.MultiLineString, "MultiPolygon": QgsWkbTypes.MultiPolygon, } _PROMOTE_GEOM = { "Point": "MultiPoint", "LineString": "MultiLineString", "Polygon": "MultiPolygon", } def _canonical_geom_type(geom_type_str: str) -> str: """Map single-part types to their multi- equivalent for consistent grouping.""" return _PROMOTE_GEOM.get(geom_type_str, geom_type_str) QVARIANT_MAP = { bool: QVariant.Bool, int: QVariant.LongLong, float: QVariant.Double, str: QVariant.String, } def _ensure_mlt(): if mlt is None: raise ImportError( "mlt module not found. " "Install it with: pip install mlt " "(or build from rust/mlt-py with maturin)" ) def _infer_field_type(value) -> int: return QVARIANT_MAP.get(type(value), QVariant.String) def _qgs_geom_type_string(wkb_type) -> str: mapping = { QgsWkbTypes.Point: "Point", QgsWkbTypes.LineString: "LineString", QgsWkbTypes.Polygon: "Polygon", QgsWkbTypes.MultiPoint: "MultiPoint", QgsWkbTypes.MultiLineString: "MultiLineString", QgsWkbTypes.MultiPolygon: "MultiPolygon", } return mapping.get(wkb_type, "Point") def _group_features_by_geom_type(mlt_layer): groups = {} for feat in mlt_layer.features: gt = _canonical_geom_type(feat.geometry_type) groups.setdefault(gt, []).append(feat) return groups def _discover_fields(features) -> QgsFields: field_types = {} for feat in features: props = feat.properties for key, val in props.items(): if val is not None and key not in field_types: field_types[key] = _infer_field_type(val) fields = QgsFields() for name, vtype in sorted(field_types.items()): fields.append(QgsField(name, vtype)) return fields def _make_qgs_features(features, vl, field_names) -> List[QgsFeature]: qgs_features = [] for feat in features: qf = QgsFeature(vl.fields()) geom = QgsGeometry() geom.fromWkb(bytes(feat.wkb)) geom.convertToMultiType() qf.setGeometry(geom) for name in field_names: val = feat.properties.get(name) if val is not None: qf.setAttribute(name, val) qgs_features.append(qf) return qgs_features def _decode_file(file_path, zxy=None, tms=True): data = Path(file_path).read_bytes() if zxy is not None: return mlt.decode_mlt(data, z=zxy[0], x=zxy[1], y=zxy[2], tms=tms) return mlt.decode_mlt(data) def _create_and_populate_layer( layer_name: str, geom_type_str: str, features: list, crs: str = "EPSG:3857", ) -> Optional[QgsVectorLayer]: """Create a memory layer, populate it with features, and add to the project.""" wkb_type = GEOM_TYPE_MAP.get(geom_type_str) if wkb_type is None: QgsMessageLog.logMessage( f"Skipping unknown geometry type: {geom_type_str}", PLUGIN_NAME, Qgis.Warning, ) return None fields = _discover_fields(features) geom_str = _qgs_geom_type_string(wkb_type) uri = f"{geom_str}?crs={crs}&index=yes" if crs else f"{geom_str}?index=yes" vl = QgsVectorLayer(uri, layer_name, "memory") pr = vl.dataProvider() pr.addAttributes(fields.toList()) vl.updateFields() field_names = [fields.at(i).name() for i in range(fields.count())] pr.addFeatures(_make_qgs_features(features, vl, field_names)) vl.updateExtents() QgsProject.instance().addMapLayer(vl) QgsMessageLog.logMessage( f"Loaded layer '{layer_name}' with {len(features)} features", PLUGIN_NAME, Qgis.Info, ) return vl # ── Public API ──────────────────────────────────────────────────────── def load_mlt_file( file_path: str, zxy: Optional[Tuple[int, int, int]] = None, tms: bool = True, ) -> List[QgsVectorLayer]: """Read a single MLT file and add its layers to the QGIS project.""" _ensure_mlt() mlt_layers = _decode_file(file_path, zxy=zxy, tms=tms) stem = Path(file_path).stem result = [] for mlt_layer in mlt_layers: groups = _group_features_by_geom_type(mlt_layer) for geom_type_str, features in groups.items(): name = f"{stem} \u2014 {mlt_layer.name}" if len(groups) > 1: name += f" ({geom_type_str})" crs = "EPSG:3857" if zxy is not None else "" vl = _create_and_populate_layer(name, geom_type_str, features, crs=crs) if vl: result.append(vl) return result def load_mlt_files_merged( file_paths: List[str], file_coords: Optional[Dict[str, Tuple[int, int, int]]] = None, tms: bool = True, ) -> List[QgsVectorLayer]: """Read multiple MLT files and merge same-named layers into single QGIS layers. Features from every tile that share the same MLT layer name and geometry type are combined into one QGIS memory layer, giving a seamless multi-tile view. Args: file_paths: List of .mlt file paths. file_coords: Optional dict {path: (z, x, y)} for geo-referencing. Files not in the dict (or if None) use raw tile coords. tms: TMS y-axis convention (default True). """ _ensure_mlt() # Key: (layer_name, geom_type_str) -> list of mlt features buckets: Dict[Tuple[str, str], list] = defaultdict(list) for path in file_paths: zxy = file_coords.get(path) if file_coords else None mlt_layers = _decode_file(path, zxy=zxy, tms=tms) for mlt_layer in mlt_layers: for feat in mlt_layer.features: bucket_key = (mlt_layer.name, _canonical_geom_type(feat.geometry_type)) buckets[bucket_key].append(feat) result = [] n_files = len(file_paths) label = f"{n_files} tiles" for (layer_name, geom_type_str), features in sorted(buckets.items()): display_name = f"{label} \u2014 {layer_name}" # Only disambiguate by geom type if the same layer name has multiple types sibling_types = [ gt for (ln, gt) in buckets if ln == layer_name ] if len(sibling_types) > 1: display_name += f" ({geom_type_str})" crs = "EPSG:3857" if file_coords else "" vl = _create_and_populate_layer(display_name, geom_type_str, features, crs=crs) if vl: result.append(vl) return result ================================================ FILE: qgis/mlt_plugin/metadata.txt ================================================ [general] name=MLT Provider qgisMinimumVersion=3.22 description=Open MapLibre Tile (MLT) files in QGIS using high-performance Rust decoding via mlt-py version=0.1.0 author=MapLibre Contributors email=info@maplibre.org about=Adds native support for the MapLibre Tile (MLT) columnar vector tile format. Uses Rust-based decoding (mlt) exposed through PyO3 for fast, reliable parsing. Features include drag-and-drop .mlt file loading, per-layer vector display, and full attribute support. tracker=https://github.com/maplibre/maplibre-tile-spec/issues repository=https://github.com/maplibre/maplibre-tile-spec tags=vector tiles,mlt,maplibre,tiles homepage=https://github.com/maplibre/maplibre-tile-spec category=Vector icon= experimental=True deprecated=False changelog= 0.1.0 - Initial release with MLT file reading support ================================================ FILE: qgis/mlt_plugin/plugin.py ================================================ """QGIS Plugin class that registers the MLT file handler.""" from pathlib import Path from qgis.PyQt.QtGui import QIcon from qgis.PyQt.QtWidgets import QAction, QFileDialog from qgis.core import Qgis, QgsMessageLog from .loader import load_mlt_file, load_mlt_files_merged from .tile_coords import ( MultipleTileCoordDialog, TileCoordDialog, parse_zxy_from_path, ) PLUGIN_NAME = "MLT Provider" class MltPlugin: def __init__(self, iface): self.iface = iface self.action = None def initGui(self): icon = QIcon() self.action = QAction(icon, "Open MLT File(s)\u2026", self.iface.mainWindow()) self.action.triggered.connect(self.open_file_dialog) self.iface.addToolBarIcon(self.action) self.iface.addPluginToVectorMenu(PLUGIN_NAME, self.action) def unload(self): if self.action: self.iface.removePluginVectorMenu(PLUGIN_NAME, self.action) self.iface.removeToolBarIcon(self.action) def open_file_dialog(self): paths, _ = QFileDialog.getOpenFileNames( self.iface.mainWindow(), "Open MapLibre Tile(s)", "", "MLT Files (*.mlt);;All Files (*)", ) if not paths: return if len(paths) == 1: self._load_single(paths[0]) else: self._load_multiple(paths) def _load_single(self, path: str): guess = parse_zxy_from_path(path) dlg = TileCoordDialog(self.iface.mainWindow(), initial=guess) zxy = None tms = True if dlg.exec_(): zxy = dlg.zxy() tms = dlg.tms elif not dlg.skipped: return try: layers = load_mlt_file(path, zxy=zxy, tms=tms) self._report_result(layers, [path], zxy is not None) except Exception as exc: self._report_error(exc) def _load_multiple(self, paths: list): files_with_coords = [ (p, parse_zxy_from_path(p)) for p in paths ] dlg = MultipleTileCoordDialog( self.iface.mainWindow(), files_with_coords=files_with_coords ) use_coords = False tms = True merge = True file_coords = None if dlg.exec_(): use_coords = True tms = dlg.tms merge = dlg.merge file_coords = dlg.file_coords() elif not dlg.skipped: return else: merge = True try: if merge: layers = load_mlt_files_merged( paths, file_coords=file_coords if use_coords else None, tms=tms, ) else: layers = [] for p in paths: zxy = file_coords.get(p) if file_coords else None layers.extend(load_mlt_file(p, zxy=zxy, tms=tms)) self._report_result(layers, paths, use_coords) except Exception as exc: self._report_error(exc) def _report_result(self, layers, paths, georeferenced): if not layers: self.iface.messageBar().pushMessage( PLUGIN_NAME, "No layers found in the MLT file(s).", level=Qgis.Warning, duration=5, ) else: n_files = len(paths) file_str = "file" if n_files == 1 else f"{n_files} files" coord_msg = " (georeferenced)" if georeferenced else " (raw tile coords)" self.iface.messageBar().pushMessage( PLUGIN_NAME, f"Loaded {len(layers)} layer(s) from {file_str}{coord_msg}", level=Qgis.Info, duration=5, ) def _report_error(self, exc): QgsMessageLog.logMessage(str(exc), PLUGIN_NAME, Qgis.Critical) self.iface.messageBar().pushMessage( PLUGIN_NAME, f"Failed to load MLT file: {exc}", level=Qgis.Critical, duration=10, ) ================================================ FILE: qgis/mlt_plugin/tile_coords.py ================================================ """Parse and prompt for tile z/x/y coordinates.""" import re from pathlib import Path from typing import Dict, List, Optional, Tuple from qgis.PyQt.QtCore import Qt from qgis.PyQt.QtWidgets import ( QCheckBox, QDialog, QDialogButtonBox, QFormLayout, QHeaderView, QLabel, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, ) ZXY = Tuple[int, int, int] _FNAME_RE = re.compile(r"(\d{1,2})[_\-](\d+)[_\-](\d+)") def parse_zxy_from_path(file_path: str) -> Optional[ZXY]: """Try to extract z/x/y from the file name or parent directories. Recognises: - 14_8297_10749.mlt (underscore-separated) - 14-8297-10749.mlt (dash-separated) - .../14/8297/10749.mlt (directory hierarchy) """ p = Path(file_path) m = _FNAME_RE.search(p.stem) if m: return int(m.group(1)), int(m.group(2)), int(m.group(3)) try: y_val = int(p.stem) x_val = int(p.parent.name) z_val = int(p.parent.parent.name) if 0 <= z_val <= 30: return z_val, x_val, y_val except (ValueError, IndexError): pass return None class TileCoordDialog(QDialog): """Dialog for a single tile — enter or confirm z/x/y coordinates.""" def __init__(self, parent=None, initial: Optional[ZXY] = None): super().__init__(parent) self.setWindowTitle("MLT Tile Coordinates") self.setMinimumWidth(320) layout = QVBoxLayout(self) info = QLabel( "Vector tiles use local coordinates. To place features\n" "on the map correctly, enter the tile's z/x/y address." ) info.setWordWrap(True) layout.addWidget(info) form = QFormLayout() self.z_spin = QSpinBox() self.z_spin.setRange(0, 30) self.z_spin.setValue(initial[0] if initial else 14) form.addRow("Zoom (z):", self.z_spin) self.x_spin = QSpinBox() self.x_spin.setRange(0, 2**30 - 1) self.x_spin.setValue(initial[1] if initial else 0) form.addRow("Column (x):", self.x_spin) self.y_spin = QSpinBox() self.y_spin.setRange(0, 2**30 - 1) self.y_spin.setValue(initial[2] if initial else 0) form.addRow("Row (y):", self.y_spin) self.tms_check = QCheckBox("TMS y-axis (y=0 at south)") self.tms_check.setChecked(True) self.tms_check.setToolTip( "Check for OpenMapTiles, MBTiles, TileJSON sources.\n" "Uncheck for OSM slippy-map / XYZ tiles (y=0 at north)." ) form.addRow("Convention:", self.tms_check) layout.addLayout(form) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) skip = buttons.addButton("Skip (raw coords)", QDialogButtonBox.RejectRole) skip.clicked.connect(self._on_skip) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) layout.addWidget(buttons) self._skipped = False def _on_skip(self): self._skipped = True self.reject() @property def skipped(self) -> bool: return self._skipped def zxy(self) -> ZXY: return self.z_spin.value(), self.x_spin.value(), self.y_spin.value() @property def tms(self) -> bool: return self.tms_check.isChecked() class MultipleTileCoordDialog(QDialog): """Dialog for multiple tiles — shows an editable table of detected z/x/y per file.""" def __init__( self, parent=None, files_with_coords: Optional[List[Tuple[str, Optional[ZXY]]]] = None, ): super().__init__(parent) self.setWindowTitle("MLT Tile Coordinates") self.setMinimumWidth(600) self.setMinimumHeight(400) self._files = files_with_coords or [] layout = QVBoxLayout(self) detected = sum(1 for _, c in self._files if c is not None) total = len(self._files) info = QLabel( f"{total} tiles selected. Coordinates auto-detected for " f"{detected}/{total} files.\n" "Edit the table below to correct any values." ) info.setWordWrap(True) layout.addWidget(info) self.table = QTableWidget(total, 4) self.table.setHorizontalHeaderLabels(["File", "z", "x", "y"]) header = self.table.horizontalHeader() header.setSectionResizeMode(0, QHeaderView.Stretch) for col in (1, 2, 3): header.setSectionResizeMode(col, QHeaderView.ResizeToContents) for row, (path, coords) in enumerate(self._files): name_item = QTableWidgetItem(Path(path).name) name_item.setFlags(name_item.flags() & ~Qt.ItemIsEditable) self.table.setItem(row, 0, name_item) z_val, x_val, y_val = coords if coords else (0, 0, 0) z_spin = QSpinBox() z_spin.setRange(0, 30) z_spin.setValue(z_val) self.table.setCellWidget(row, 1, z_spin) x_spin = QSpinBox() x_spin.setRange(0, 2**30 - 1) x_spin.setValue(x_val) self.table.setCellWidget(row, 2, x_spin) y_spin = QSpinBox() y_spin.setRange(0, 2**30 - 1) y_spin.setValue(y_val) self.table.setCellWidget(row, 3, y_spin) layout.addWidget(self.table) self.tms_check = QCheckBox("TMS y-axis (y=0 at south)") self.tms_check.setChecked(True) self.tms_check.setToolTip( "Check for OpenMapTiles, MBTiles, TileJSON sources.\n" "Uncheck for OSM slippy-map / XYZ tiles (y=0 at north)." ) layout.addWidget(self.tms_check) self.merge_check = QCheckBox("Merge same-named layers across tiles") self.merge_check.setChecked(True) self.merge_check.setToolTip( "When checked, layers with the same name from different tiles\n" "are combined into a single QGIS layer for seamless viewing." ) layout.addWidget(self.merge_check) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) skip = buttons.addButton("Skip (raw coords)", QDialogButtonBox.RejectRole) skip.clicked.connect(self._on_skip) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) layout.addWidget(buttons) self._skipped = False def _on_skip(self): self._skipped = True self.reject() @property def skipped(self) -> bool: return self._skipped @property def tms(self) -> bool: return self.tms_check.isChecked() @property def merge(self) -> bool: return self.merge_check.isChecked() def file_coords(self) -> Dict[str, Optional[ZXY]]: """Return {file_path: (z, x, y)} for each file.""" result = {} for row, (path, _) in enumerate(self._files): z = self.table.cellWidget(row, 1).value() x = self.table.cellWidget(row, 2).value() y = self.table.cellWidget(row, 3).value() result[path] = (z, x, y) return result ================================================ FILE: release-plz.toml ================================================ [workspace] allow_dirty = false changelog_update = true dependencies_update = true git_release_enable = true git_release_name = "rust-{{ package }}-v{{ version }}" git_release_body = """ {{ changelog }} {% if remote.contributors %} ### Contributors {% for contributor in remote.contributors %} * @{{ contributor.username }} {% endfor %} {% endif %} """ git_tag_enable = true git_tag_name = "rust-{{ package }}-v{{ version }}" pr_branch_prefix = "release-plz-" pr_labels = ["release"] pr_name = "chore(rust): release{% if package %} {{ package }}{% endif %}{% if version %} v{{ version }}{% endif %}" publish_allow_dirty = false publish_no_verify = true # we have already thoroughly verified that all aspects of our project work in CI before this step semver_check = true publish_timeout = "10m" # set a timeout for `cargo publish` publish = true [[package]] name = "mlt" [[package]] name = "mlt-core" [[package]] name = "mlt-py" git_tag_name = "python-mlt-v{{ version }}" git_release_name = "python-mlt-v{{ version }}" [[package]] name = "mlt-synthetics" publish = false [changelog] # always include commits with breaking changes in the changelog protect_breaking_commits = true ================================================ FILE: rust/.gitignore ================================================ **/*.rs.bk *.pdb *.proptest-regressions .idea/ .vscode/ debug/ proptest-regressions/ target/ temp/ tmp/ venv/ rustc-ice-*.txt ================================================ FILE: rust/CONTRIBUTING.md ================================================ # Contributing to Rust MLT ## Development Workflow All commands must be run from the repository root using [`just`](https://github.com/casey/just). Do **not** use bare `cargo` commands; `just` recipes ensure the correct feature flags and CI-matching environments. Before committing, make sure that `just rust::fmt`, `just rust::lint`, and `just rust::test` pass. | Command | Description | |:-------------------------------------|:------------------------------------------------------------------------| | `just rust::check` | Fast compilation check (no binaries). | | `just rust::test` | Run full test suite (including property-based tests). | | `just rust::fmt` | Format code according to project style. | | `just rust::lint` | Run `clippy` lints. | | `just rust::generate-synthetic-mlts` | Generate synthetic MLT files for testing. Do not delete files manually. | | `just rust::bless` | Regenerate expected `insta` test snapshots. | ## Code Structure * Most structs and enums live in `model.rs` files, while the specific functionality for those types live in impl blocks of various files like `decoding.rs`, `encoding.rs`, etc. This is not very Rust-idiomatic, but it allows for better organization of the codebase by feature (e.g., all decoding logic in one file, all encoding logic in another file) rather than by type. * We try to keep encoding and decoding logic separate, as they have different requirements and optimizations. For example, decoding can be more flexible and allow for partial lazy decoding, while encoding needs to operate on owned data structures and may require more complex transformations and optimizations (e.g., reordering features for better compression). ## Decoding Data When decoding data, `mlt-core` moves through a strict linear pipeline, minimizing unnecessary allocations and copies. Both raw and parsed data is stored in the container structs (e.g., `TileLayer`) as variants of the `LazyParsed` generic enum to allow for partial lazy decoding for any column like `id`, `geometry`, `property`, `sub-property`. Some internal owned types may be used inside both the `Parsed*` (decoding) and `Staged*` (encoding) data structures. `Parsed*` structs should not be created in any way other than from the `Raw*` structs. When testing, create corresponding `Staged*` struct to compare with the Parsed* ones - there should be an equality trait for them all. Some types might be shared between encoding and decoding types. If shared, these types should not have any intermediate prefixes, i.e. raw, parsed, staged, or encoded. If conflicted, the type should be named like `Owned*` or just by its name without a prefix. | Stage | Prefix | Ownership | Purpose | |:------|:---------------|:-------------------------|:------------------------------------------------------------------------------| | **-** | `` | Borrowed (`&'a`) | Container structs to allow partial lazy decoding. | | **1** | `Raw` | Borrowed (`&'a`) | Zero-copy views of input bytes. No allocation. | | **2** | `Parsed` | Owned or Borrowed (`'a`) | Fully decoded Rust values (e.g., `Vec`), or could reference input bytes. | | **3** | `Tile*` | Owned | **Row-oriented** features using `mlt_core::geo_types::Geometry` for geometries. | ### Notes * **Forward Only:** Data moves `1` -> `3`. No backwards conversions (e.g., `Tile*` cannot become `Parsed*`). * **Slicing:** Original input bytes are sliced into `Raw*` structures. No copying. Uses `Raw*::parse(&[u8])` -> `MltRefResult` constructors. Not to be confused with `Parsed*` parsing into Parsed stage. * **Parsing:** `Raw*` `->` `Parsed*` via `TryFrom`. * **Row-based tiles:** `Parsed*` `->` `Tile*` via `TryFrom`. This is mostly used for GeoJSON generation by CLI and debugging tools, and should probably not be needed for most users like data access via WASM. * **No 'static** All types in the decoding pipeline should have real lifetimes tied to original byte buffer, not `'static`. For testing, simply create corresponding `Staged*` instance to compare. ## Encoding Data Encoding is more complex, and requires owned data structures to support optimizations and transformations. The pipeline is as follows: | Stage | Prefix | Ownership | Purpose | |:------|:-----------|:----------------------|:---------------------------------------------------------------------------| | **1** | `Tile*` | Owned | **Row-oriented** features using `geo_types::Geometry` for geometries. | | **2** | `Staged*` | Owned | **Columnar** data being prepared for encoding. | | **3** | `Encoded*` | Owned | Wire-ready byte buffers. | ### Notes * **Forward Only:** Data moves `1` -> `3`. No backwards conversions (e.g., `Encoded` cannot become `Staged`). * All progression steps will use an `Encoder*` types that has some state of **how** to encode, the data of the stage, and return the next stage types, e.g. `TileLayer` via `Tile01Encoder::encode(&mut self, data: &mut TileLayer)` -> `MltResult` `->` `StagedLayer`. Note that both `self` and `data` are mutable references, as the encoder may need to mutate internal state and the data being encoded (e.g., for optimizations like reordering features or updating profiling information). * **Staging:** `Tile*` `->` `Staged*`. * **Encoding:** `Staged*` `->` `Encoded*`. * **Serialization:** `Encoded*` `->` bytes via `write_to(&mut writer)`. * In some use cases, user may want to construct `Staged*` directly (e.g., to generate synthetic MLT files or in benchmarking), and skip the `Tile*` stage. * There should not be a need to convert from `Parsed*` to `Staged*`, as the former is for decoding and the latter is for encoding. For round trip testing, the workflow should be `Tile*` `->` `Staged*` `->` `Encoded*` `->` `Vec` buffer `->` `Raw*` `->` `Parsed*` `->` `Tile*`. It should be possible to compare `Staged*` and `Parsed*` for testing purposes, but not convert from one to another. * **No decode when encoding:** The encoder moves only from higher-level to lower-level (e.g. it may try multiple sortings or encodings at `TileLayer` level and keep the best result). It must never decode encoded data back into a decoded representation. Round-trip verification belongs in tests only, via the full bytes pipeline (encode → bytes → parse → decode). ================================================ FILE: rust/Cargo.toml ================================================ [workspace] resolver = "3" members = ["mlt", "mlt-core", "mlt-py", "mlt-synthetics", "mlt-wasm"] default-members = ["mlt", "mlt-core", "mlt-synthetics"] # Excluding mlt-core/fuzz because cargo-fuzz does not have workspace support exclude = ["mlt-core/fuzz"] [workspace.package] authors = ["Maplibre Contributors"] categories = ["science::geo"] edition = "2024" homepage = "https://maplibre.org" keywords = ["maplibre", "tile", "mlt", "mvt", "map"] license = "MIT OR Apache-2.0" repository = "https://github.com/maplibre/maplibre-tile-spec" rust-version = "1.92" [workspace.dependencies] anyhow = "1" arbitrary = { version = "1.4", features = ["derive"] } bitvec = "1" brotli = "8" bytemuck = "1.25.0" bytes = "1" clap = { version = "4.6.0", features = ["derive"] } criterion = { version = "0.8", features = ["html_reports"] } crossterm = "0.29.0" derive-debug = "0.1.2" enum_dispatch = "0.3" fastpfor = { version = "0.9", features = ["rust"] } flate2 = "1" fsst-rs = "0.5" futures = "0.3" geo = { version = "0.33.1", default-features = false } geo-types = "0.7.19" glob = "0.3" globset = "0.4" hex = "0.4.3" hilbert_2d = "1.1.0" hotpath = "0.15" indicatif = "0.18" insta = "1.47.2" integer-encoding = "4.0.2" js-sys = "0.3" martin-tile-utils = "0.7" mbtiles = { version = "0.17", default-features = false, features = ["transcode"] } mlt-core = { version = "0.9.0", path = "mlt-core" } moka = { version = "0.12", features = ["sync"] } mvt-reader = "2.3.0" num-traits = "0.2.19" num_enum = "0.7.6" pretty_assertions = "1.4" probabilistic-collections = "0.7" proptest = "1.11" proptest-derive = "0.8" pyo3 = "0.28.3" pyo3-stub-gen = "0.22.2" ratatui = "0.30.0" rayon = "1" rstar = "0.12" rstest = "0.26.1" serde = { version = "1", features = ["derive"] } serde_json = "1.0.149" size_format = "1" sqlx = { version = "0.8", default-features = false, features = ["sqlite", "runtime-tokio"] } strum = { version = "0.28.0", default-features = false, features = ["derive"] } tabled = "0.20" test_each_file = "0.3.5" thiserror = "2.0.11" thousands = "0.2" tokio = { version = "1", features = ["macros", "rt", "sync", "time"] } union-find = "0.4.4" walkdir = "2" wasm-bindgen = "0.2" wide = "1.1.1" xxhash-rust = { version = "0.8", features = ["xxh3"] } zigzag = "0.1.0" zstd = "0.13" [workspace.lints.rust] unsafe_code = "forbid" unused_qualifications = "warn" unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } [workspace.lints.clippy] cargo = { level = "warn", priority = -1 } pedantic = { level = "warn", priority = -1 } # Restrictions panic_in_result_fn = "warn" todo = "warn" unimplemented = "warn" use_self = "warn" # Too noisy missing_errors_doc = "allow" missing_panics_doc = "allow" multiple_crate_versions = "allow" too_many_lines = "allow" ================================================ FILE: rust/README.md ================================================ # Rust based MLT tooling and libraries In this directory you will find Rust based libraries and tools for MLT. - [`mlt-core`](./mlt-core/README.md) is the rust library for MLT. - [`mlt`](./mlt/README.md) is our CLI tool for exploring and working with MLTs. ================================================ FILE: rust/bench_param.sh ================================================ #!/usr/bin/env bash # Usage: ./bench_param.sh [value2] ... # # uses \1 as the placeholder for the value. # The script substitutes each value, rebuilds, runs N iterations, # and reports avg size ± spread and avg time ± spread. # # Examples: # ./bench_param.sh mlt-core/src/encoder/geometry/encode.rs \ # 's/MORTON_UNIQUENESS_THRESHOLD: f64 = .*/MORTON_UNIQUENESS_THRESHOLD: f64 = \1;/' \ # 0.3 0.4 0.5 0.6 # # ./bench_param.sh mlt-core/src/encoder/property/strings.rs \ # 's/FSST_OVERHEAD_THRESHOLD: usize = .*/FSST_OVERHEAD_THRESHOLD: usize = \1;/' \ # 2_048 4_096 8_192 set -euo pipefail RUNS=1 INPUT="../data/germany.mbtiles" FILE="$1"; shift SED_TEMPLATE="$1"; shift VALUES=("$@") if [[ ${#VALUES[@]} -eq 0 ]]; then echo "Usage: $0 [value2] ..." exit 1 fi printf "%-20s %12s %12s %12s %12s\n" "Value" "Avg (bytes)" "Spread" "Avg (ms)" "Spread (ms)" printf '%.0s─' {1..72}; echo for val in "${VALUES[@]}"; do sed_cmd="${SED_TEMPLATE//\\1/$val}" sed -i "$sed_cmd" "$FILE" cargo build --release -p mlt 2>/dev/null sizes=() times=() for ((i=1; i<=RUNS; i++)); do out="/tmp/bench_param_$$.mbtiles" rm -f "$out" start_ns=$(date +%s%N) ./target/release/mlt convert "$INPUT" "$out" 1>/dev/null end_ns=$(date +%s%N) elapsed_ms=$(( (end_ns - start_ns) / 1000000 )) times+=("$elapsed_ms") s=$(stat --printf='%s' "$out") sizes+=("$s") rm -f "$out" done size_csv=$(IFS=,; echo "${sizes[*]}") time_csv=$(IFS=,; echo "${times[*]}") read -r savg smn smx tavg tmn tmx < <(python3 -c " s = [$size_csv] t = [$time_csv] print(sum(s)//len(s), min(s), max(s), sum(t)//len(t), min(t), max(t)) ") sspread=$((smx - smn)) tspread=$((tmx - tmn)) printf "%-20s %12s %12s %12s %12s\n" "$val" "$savg" "±$sspread" "${tavg}ms" "±${tspread}ms" done ================================================ FILE: rust/clippy.toml ================================================ allow-unwrap-in-tests = true avoid-breaking-exported-api = false disallowed-methods = [ "alloc::boxed::Box::leak", "alloc::vec::Vec::leak", ] ================================================ FILE: rust/mlt/CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [0.1.11](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-v0.1.10...rust-mlt-v0.1.11) - 2026-04-29 ### Added - *(rust)* add secondary Hilbert dictionary sorting on select tiles ([#1349](https://github.com/maplibre/maplibre-tile-spec/pull/1349)) ### Other - *(rust)* IdValues→ParsedId+StagedId, simplify presence ([#1334](https://github.com/maplibre/maplibre-tile-spec/pull/1334)) - *(rust)* Improve the performance of `mlt convert` by caching and special-casing ([#1286](https://github.com/maplibre/maplibre-tile-spec/pull/1286)) ## [0.1.10](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-v0.1.9...rust-mlt-v0.1.10) - 2026-04-18 ### Added - *(rust)* mlt UI to browse .mbtiles ([#1306](https://github.com/maplibre/maplibre-tile-spec/pull/1306)) ### Other - remove ALP everywhere - it was never implemented ([#1128](https://github.com/maplibre/maplibre-tile-spec/pull/1128)) - *(rust)* rm RawStreamData and EncodedStreamData ([#1309](https://github.com/maplibre/maplibre-tile-spec/pull/1309)) - *(rust)* Coord32 cleanup, dep update ([#1304](https://github.com/maplibre/maplibre-tile-spec/pull/1304)) - Add offline docs.rs-style workspace docs check to Rust CI and fix surfaced rustdoc links ([#1295](https://github.com/maplibre/maplibre-tile-spec/pull/1295)) ## [0.1.9](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-v0.1.8...rust-mlt-v0.1.9) - 2026-04-13 ### Added - *(rust)* mlt convert support for full mbtiles conversion ([#1259](https://github.com/maplibre/maplibre-tile-spec/pull/1259)) - *(rust)* mlt convert, encoder ([#1240](https://github.com/maplibre/maplibre-tile-spec/pull/1240)) ### Other - *(rust)* remove a few bad performance choices (+6% encoder gain) ([#1287](https://github.com/maplibre/maplibre-tile-spec/pull/1287)) - benchmark and fix various performance issues in the encoder ([#1273](https://github.com/maplibre/maplibre-tile-spec/pull/1273)) - *(rust)* Hotpath based profiling ([#1269](https://github.com/maplibre/maplibre-tile-spec/pull/1269)) - *(rust)* massive rewrite of the encoder ([#1254](https://github.com/maplibre/maplibre-tile-spec/pull/1254)) - *(rust)* consolidate utils ([#1248](https://github.com/maplibre/maplibre-tile-spec/pull/1248)) - *(rust)* move frames/v01 -> decoder, adj use ([#1246](https://github.com/maplibre/maplibre-tile-spec/pull/1246)) - *(rust)* move more code to encoder ([#1245](https://github.com/maplibre/maplibre-tile-spec/pull/1245)) - *(rust)* mv tessellation to core ([#1220](https://github.com/maplibre/maplibre-tile-spec/pull/1220)) - *(rust)* improve synthetics, geojson ([#1210](https://github.com/maplibre/maplibre-tile-spec/pull/1210)) - add large FastPFOR synthetics ([#1205](https://github.com/maplibre/maplibre-tile-spec/pull/1205)) - *(rust)* implement feature/property iterator and more type state ([#1198](https://github.com/maplibre/maplibre-tile-spec/pull/1198)) - *(rust)* type state to represent fully-decoded layers ([#1171](https://github.com/maplibre/maplibre-tile-spec/pull/1171)) ## [0.1.8](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-v0.1.7...rust-mlt-v0.1.8) - 2026-03-23 ### Other - *(rust)* migrate to Rust fastpfor ([#1190](https://github.com/maplibre/maplibre-tile-spec/pull/1190)) ## [0.1.7](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-v0.1.6...rust-mlt-v0.1.7) - 2026-03-17 ### Other - *(rust)* memory budgeting, codecs ([#1168](https://github.com/maplibre/maplibre-tile-spec/pull/1168)) - *(rust)* minor noop cleanup ([#1167](https://github.com/maplibre/maplibre-tile-spec/pull/1167)) - *(rust)* rename EncDec variants ([#1165](https://github.com/maplibre/maplibre-tile-spec/pull/1165)) - *(rust)* add stateful decoder ([#1163](https://github.com/maplibre/maplibre-tile-spec/pull/1163)) - *(rust)* get rid of borrowme, add EncDec enum ([#1141](https://github.com/maplibre/maplibre-tile-spec/pull/1141)) ## [0.1.6](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-v0.1.5...rust-mlt-v0.1.6) - 2026-03-10 ### Fixed - *(rust)* migrate part of our lengths from usize to u32 ([#1078](https://github.com/maplibre/maplibre-tile-spec/pull/1078)) ### Other - *(rust)* rework our internal data model ([#1099](https://github.com/maplibre/maplibre-tile-spec/pull/1099)) ## [0.1.5](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-v0.1.4...rust-mlt-v0.1.5) - 2026-03-08 ### Other - *(rust)* bump fastpfor, enable SIMD test, cleanup ([#1052](https://github.com/maplibre/maplibre-tile-spec/pull/1052)) ## [0.1.4](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-v0.1.3...rust-mlt-v0.1.4) - 2026-03-04 ### Fixed - *(rust)* LineString and MultiLineString geometries ([#989](https://github.com/maplibre/maplibre-tile-spec/pull/989)) ### Other - *(rust)* rename IntEncoder and add IntEncoding ([#1019](https://github.com/maplibre/maplibre-tile-spec/pull/1019)) - rename `Encoder` -> `IntegerEncoder` ([#1010](https://github.com/maplibre/maplibre-tile-spec/pull/1010)) - *(synthetic)* add Morton fixture synthetic test ([#960](https://github.com/maplibre/maplibre-tile-spec/pull/960)) - *(rust)* rm json5, inf floats to string ([#1000](https://github.com/maplibre/maplibre-tile-spec/pull/1000)) ## [0.1.3](https://github.com/maplibre/maplibre-tile-spec/compare/mlt-v0.1.2...mlt-v0.1.3) - 2026-02-25 ### Fixed - *(rust)* bugs in mlt UI visualizer ([#970](https://github.com/maplibre/maplibre-tile-spec/pull/970)) ## [0.1.2](https://github.com/maplibre/maplibre-tile-spec/compare/mlt-v0.1.1...mlt-v0.1.2) - 2026-02-25 ### Added - *(rust)* mlt ls wildcards, exclusions, throttle hover redraws, dependencies ([#967](https://github.com/maplibre/maplibre-tile-spec/pull/967)) - *(rust)* validate mlt-json with ls command ([#963](https://github.com/maplibre/maplibre-tile-spec/pull/963)) ### Other - *(rust)* fix another CI typo ([#959](https://github.com/maplibre/maplibre-tile-spec/pull/959)) ## [0.1.1](https://github.com/maplibre/maplibre-tile-spec/compare/mlt-v0.1.0...mlt-v0.1.1) - 2026-02-25 ### Added - *(rust)* add tile preview in cli dir view ([#943](https://github.com/maplibre/maplibre-tile-spec/pull/943)) ### Other - *(rust)* more renames ([#945](https://github.com/maplibre/maplibre-tile-spec/pull/945)) - *(rust)* rename encoding-related types for clarity ([#944](https://github.com/maplibre/maplibre-tile-spec/pull/944)) - *(rust)* optimize some UI code ([#939](https://github.com/maplibre/maplibre-tile-spec/pull/939)) - *(rust)* rename LogicalDecoder and PhysicalDecoder to codecs ([#923](https://github.com/maplibre/maplibre-tile-spec/pull/923)) - *(rust)* add basic benchmark infrastructure ([#912](https://github.com/maplibre/maplibre-tile-spec/pull/912)) - *(rust)* more cleanups ([#914](https://github.com/maplibre/maplibre-tile-spec/pull/914)) - *(ci)* cleanup ci workflows ([#908](https://github.com/maplibre/maplibre-tile-spec/pull/908)) ================================================ FILE: rust/mlt/Cargo.toml ================================================ [package] name = "mlt" description = "MapLibre Tile Tools" version = "0.1.11" authors.workspace = true categories.workspace = true edition.workspace = true homepage.workspace = true keywords.workspace = true license.workspace = true repository.workspace = true rust-version.workspace = true [package.metadata.binstall] pkg-url = "{ repo }/releases/download/rust-{ name }-v{ version }/{ name }-{ target }{ archive-suffix }" pkg-fmt = "tgz" [package.metadata.binstall.overrides.'cfg(target_os = "windows")'] pkg-fmt = "zip" [features] __hotpath = ["hotpath/hotpath", "hotpath/tokio", "hotpath/threads", "hotpath/tui"] __hotpath-alloc = ["__hotpath", "hotpath/hotpath-alloc"] __hotpath-mcp = ["__hotpath", "hotpath/hotpath-mcp", "hotpath/hotpath-mcp-meta"] [dependencies] anyhow.workspace = true bytes.workspace = true clap.workspace = true crossterm.workspace = true flate2.workspace = true futures.workspace = true glob.workspace = true globset.workspace = true hotpath.workspace = true indicatif.workspace = true martin-tile-utils.workspace = true mbtiles.workspace = true mlt-core.workspace = true moka.workspace = true ratatui.workspace = true rayon.workspace = true rstar.workspace = true serde.workspace = true serde_json.workspace = true size_format.workspace = true sqlx.workspace = true tabled.workspace = true thousands.workspace = true tokio.workspace = true walkdir.workspace = true xxhash-rust.workspace = true [lints] workspace = true ================================================ FILE: rust/mlt/README.md ================================================ ## CLI Tool The `mlt` binary provides several commands for working with MLT files: ### Commands * **`dump`** - Parse an MLT file and dump raw layer data without decoding * **`decode`** - Parse an MLT file, decode all layers, and dump the result (supports text and `GeoJSON` output) * **`ui`** - Interactive terminal visualizer for MLT files ### Visualizer The visualizer command provides an interactive terminal-based UI for exploring MLT files: ```bash # Visualize a single MLT file cargo run -- ui path/to/file.mlt # Browse and visualize all MLT files in a directory (recursive) cargo run -- ui path/to/directory ``` **Directory Mode**: - Lists all `.mlt` files found recursively in the directory - Use `↑`/`↓` to navigate the file list - Press `Enter` to open and visualize a file - Press `Esc` to go back to file list - Press `q` to quit Features: - **Tree View Panel (left)**: Browse layers and features in a hierarchical tree - "All Layers" - shows all features from all layers - Individual layers - shows all features in that layer - Individual features - shows only the selected feature - Hovered features are highlighted with underlined green text - **Map Panel (right)**: Visual representation of the geometries - Shows the extent boundary as a thin gray rectangle - **Color coding by geometry type**: - Points: Magenta (multipoint: light magenta) - `LineStrings`: Cyan (multi-linestring: light cyan) - Polygons: Blue/Red based on winding order (multi-polygon: same) - **Polygon winding order visualization**: - Blue: Counter-clockwise rings (typically outer rings) - Red: Clockwise rings (typically holes) - Selected features: Yellow - Hovered features: White - Automatically adjusts bounds to fit all visible geometries - **Mouse Interaction**: - Hover over geometries to highlight them in the tree view - **Keyboard Navigation**: - `↑`/`k` - Move selection up - `↓`/`j` - Move selection down - `Enter` - In layer overview mode, switch to detail mode; In file browser, open selected file - `Esc` - Go back (detail → overview → file list) or quit if at top level - `q` - Quit the visualizer ================================================ FILE: rust/mlt/src/convert/files.rs ================================================ use std::ffi::OsStr; use std::fs; use std::path::Path; use std::sync::Arc; use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; use std::time::Duration; use anyhow::{Context as _, Result as AnyResult, anyhow, bail}; use indicatif::{ProgressBar, ProgressStyle}; use moka::sync::Cache; use rayon::iter::{ParallelBridge as _, ParallelIterator as _}; use size_format::SizeFormatterSI; use walkdir::WalkDir; use xxhash_rust::xxh3::xxh3_128; use super::{EncoderConfig, convert_mlt_buffer, convert_mvt_buffer, whole_rate_per_sec}; use crate::ls::is_mlt_extension; /// Only tiles below this size are tracked in the dedup cache, because /// larger tiles almost never repeat across a tileset. const MAX_TILE_TRACK_SIZE: usize = 1024; const CACHE_MAX_BYTES: u64 = 512 * 1024 * 1024; type EncodedCache = Cache>>; fn make_cache(max_bytes: u64) -> EncodedCache { Cache::builder() .max_capacity(max_bytes) .weigher(|_key, value: &Arc>| u32::try_from(value.len()).unwrap_or(u32::MAX)) .build() } #[derive(Default)] struct DedupStats { hits: AtomicU64, encoded: AtomicU64, bytes_saved: AtomicU64, } impl DedupStats { fn record_hit(&self, size: usize) { self.hits.fetch_add(1, Ordering::Relaxed); self.bytes_saved.fetch_add(size as u64, Ordering::Relaxed); } fn record_encode(&self) { self.encoded.fetch_add(1, Ordering::Relaxed); } } #[expect( clippy::cast_precision_loss, reason = "hit/miss counts are well below 2^52 for realistic tilesets" )] fn format_dedup_line(stats: &DedupStats, cache: &EncodedCache) -> String { cache.run_pending_tasks(); let hits = stats.hits.load(Ordering::Relaxed); let encoded = stats.encoded.load(Ordering::Relaxed); let bytes_saved = stats.bytes_saved.load(Ordering::Relaxed); let total = hits + encoded; let hit_rate = if total == 0 { 0.0 } else { (hits as f64 * 100.0) / (total as f64) }; format!( " dedup: {encoded} unique encoded, {hits} cached ({hit_rate:.1}% hit rate, \ ~{:.1}B of encode work skipped); cache weight {:.1}B", SizeFormatterSI::new(bytes_saved), SizeFormatterSI::new(cache.weighted_size()), ) } fn is_convert_extension(path: &Path) -> bool { matches!( path.extension().and_then(OsStr::to_str), Some("mlt" | "mvt") ) } pub fn convert_files(input: &Path, output: &Path, cfg: EncoderConfig) -> AnyResult<()> { // For a single file, use the parent so `strip_prefix` yields just the filename. let base = if input.is_dir() { input } else { input.parent().unwrap_or(Path::new(".")) }; let cache: EncodedCache = make_cache(CACHE_MAX_BYTES); let stats = DedupStats::default(); let failed = AtomicUsize::new(0); let bar = ProgressBar::new_spinner(); bar.set_style( ProgressStyle::default_spinner() .template("{spinner} {elapsed_precise} [{pos} files, {rate}] {msg}") .expect("invalid spinner template") .with_key("rate", whole_rate_per_sec), ); bar.enable_steady_tick(Duration::from_millis(100)); // `bar.println` is a no-op when hidden (non-TTY), so fall back to stderr. let emit = |msg: String| { if bar.is_hidden() { eprintln!("{msg}"); } else { bar.println(msg); } }; WalkDir::new(input) .into_iter() .filter_map(|r| match r { Ok(e) => Some(e), Err(e) => { emit(format!("warning: walkdir: {e}")); failed.fetch_add(1, Ordering::Relaxed); None } }) .filter(|e| e.file_type().is_file() && is_convert_extension(e.path())) .par_bridge() .for_each(|entry| { let in_path = entry.into_path(); let result = convert_file(&in_path, base, output, cfg, &cache, &stats); bar.inc(1); if let Err(e) = result { emit(format!("error: {}: {e:#}", in_path.display())); failed.fetch_add(1, Ordering::Relaxed); } }); bar.finish_and_clear(); let n = failed.into_inner(); if n > 0 { bail!("{n} file(s) failed to convert"); } let processed = stats.hits.load(Ordering::Relaxed) + stats.encoded.load(Ordering::Relaxed); if processed == 0 { eprintln!("No .mlt or .mvt files found in {}", input.display()); return Ok(()); } eprintln!("{}", format_dedup_line(&stats, &cache)); Ok(()) } fn convert_file( file: &Path, base: &Path, output: &Path, cfg: EncoderConfig, cache: &EncodedCache, stats: &DedupStats, ) -> AnyResult<()> { let rel = file .strip_prefix(base) .with_context(|| format!("stripping prefix from {}", file.display()))?; let out_path = output.join(rel).with_extension("mlt"); if let Some(parent) = out_path.parent() { fs::create_dir_all(parent) .with_context(|| format!("creating directory {}", parent.display()))?; } let buffer = fs::read(file).with_context(|| format!("reading {}", file.display()))?; let is_mlt = is_mlt_extension(file); let file_display = file.display().to_string(); if buffer.len() > MAX_TILE_TRACK_SIZE { let out_bytes = if is_mlt { convert_mlt_buffer(&buffer, cfg) .with_context(|| format!("converting MLT {file_display}"))? } else { convert_mvt_buffer(buffer, cfg) .with_context(|| format!("converting MVT {file_display}"))? }; stats.record_encode(); fs::write(&out_path, &out_bytes) .with_context(|| format!("writing {}", out_path.display()))?; return Ok(()); } let key = xxh3_128(&buffer); let entry = cache .entry(key) .or_try_insert_with(|| -> AnyResult>> { let out_bytes = if is_mlt { convert_mlt_buffer(&buffer, cfg) .with_context(|| format!("converting MLT {file_display}"))? } else { convert_mvt_buffer(buffer, cfg) .with_context(|| format!("converting MVT {file_display}"))? }; Ok(Arc::new(out_bytes)) }) .map_err(|e: Arc| anyhow!("{e:#}"))?; let is_fresh = entry.is_fresh(); let out_arc = entry.into_value(); if is_fresh { stats.record_encode(); } else { stats.record_hit(out_arc.len()); } fs::write(&out_path, out_arc.as_slice()) .with_context(|| format!("writing {}", out_path.display()))?; Ok(()) } ================================================ FILE: rust/mlt/src/convert/mod.rs ================================================ mod files; mod tileset; use std::path::{Path, PathBuf}; use anyhow::{Result as AnyResult, bail}; use bytes::Bytes; use clap::{Args, ValueEnum}; use indicatif::ProgressState; use martin_tile_utils::{Encoding, decode_brotli, decode_gzip, decode_zlib, decode_zstd}; use mbtiles::{MbtType, NormalizedSchema}; use mlt_core::encoder::{EncodedUnknown, Encoder, EncoderConfig}; use mlt_core::mvt::mvt_to_tile_layers; use mlt_core::{Decoder, Layer, Parser}; #[expect( clippy::cast_possible_truncation, clippy::cast_sign_loss, reason = "state.per_sec() is always non-negative and well below 2^63 tiles/sec" )] fn whole_rate_per_sec(state: &ProgressState, w: &mut dyn std::fmt::Write) { let _ = w.write_fmt(format_args!("{}/s", state.per_sec() as u64)); } /// CLI-facing subset of [`MbtType`] (hides the `hash_view` detail). #[derive(Clone, Default, ValueEnum)] enum MbtFormat { /// Single table with all tiles; no deduplication (smallest overhead) #[default] Flat, /// Single table with tiles and `MD5` hashes #[value(name = "flat-with-hash")] FlatWithHash, /// Separate `images` / `map` tables; identical tiles stored only once Normalized, } impl From for MbtType { fn from(f: MbtFormat) -> Self { match f { MbtFormat::Flat => Self::Flat, MbtFormat::FlatWithHash => Self::FlatWithHash, MbtFormat::Normalized => Self::Normalized { hash_view: true, schema: NormalizedSchema::DedupId, }, } } } #[derive(Clone, Default, ValueEnum)] enum SortMode { /// Try all sort strategies and keep the smallest result #[default] Auto, /// Do not reorder features (original order only) None, /// Only try Z-order (Morton) curve sort Morton, /// Only try Hilbert curve sort Hilbert, /// Only try feature-ID ascending sort Id, } #[derive(Args)] pub struct ConvertArgs { /// Input: a directory with .mlt/.mvt tiles, a single tile file, or an .mbtiles database input: PathBuf, /// Output: a directory for re-encoded .mlt files, or an .mbtiles database (required when input is .mbtiles) output: PathBuf, /// Add tessellation #[clap(short, long, default_value = "false")] tessellate: bool, /// Sort strategy to try when re-encoding (encoder keeps the smallest result) #[clap(long, default_value = "auto")] sort: SortMode, /// Schema type for the output `.mbtiles` file; defaults to the input file's schema #[clap(long)] mbtiles_format: Option, /// Disable grouping of similar string columns into shared dictionaries #[clap(long, default_value = "false")] no_shared_dict: bool, } pub fn convert(args: &ConvertArgs) -> AnyResult<()> { let cfg = EncoderConfig { tessellate: args.tessellate, try_spatial_morton_sort: matches!(args.sort, SortMode::Auto | SortMode::Morton), try_spatial_hilbert_sort: matches!(args.sort, SortMode::Auto | SortMode::Hilbert), try_id_sort: matches!(args.sort, SortMode::Auto | SortMode::Id), allow_shared_dict: !args.no_shared_dict, ..Default::default() }; if is_mbtiles_extension(&args.input) { if !is_mbtiles_extension(&args.output) { bail!( "Output must be an .mbtiles file when input is an .mbtiles file, got: {}", args.output.display() ); } if args.output.exists() { bail!( "Output {} already exists; refusing to append. \ Delete it first or choose a different path.", args.output.display() ); } return tokio::runtime::Builder::new_current_thread() .enable_io() .enable_time() .build()? .block_on(tileset::convert_mbtiles( &args.input, &args.output, args.mbtiles_format.clone(), cfg, )); } files::convert_files(&args.input, &args.output, cfg) } fn is_mbtiles_extension(path: &Path) -> bool { matches!( path.extension().and_then(std::ffi::OsStr::to_str), Some("mbtiles") ) } fn convert_mlt_buffer(buffer: &[u8], cfg: EncoderConfig) -> AnyResult> { let layers = Parser::default().parse_layers(buffer)?; let mut dec = Decoder::default(); let mut out: Vec = Vec::new(); for layer in layers { match layer { Layer::Tag01(l) => { let tile = l.into_tile(&mut dec)?; out.extend_from_slice(&tile.encode(cfg)?); } Layer::Unknown(u) => { out.extend(EncodedUnknown::from(u).write_to(Encoder::default())?.data); } _ => {} } } Ok(out) } fn convert_mvt_buffer(buffer: Vec, cfg: EncoderConfig) -> AnyResult> { let mut out: Vec = Vec::new(); for tile in mvt_to_tile_layers(buffer)? { out.extend_from_slice(&tile.encode(cfg)?); } Ok(out) } fn encode_one(data: Vec, encoding: Encoding, cfg: EncoderConfig) -> AnyResult { let mvt = match encoding { Encoding::Gzip => decode_gzip(&data)?, Encoding::Zlib => decode_zlib(&data)?, Encoding::Brotli => decode_brotli(&data)?, Encoding::Zstd => decode_zstd(&data)?, Encoding::Uncompressed | Encoding::Internal => data, }; convert_mvt_buffer(mvt, cfg).map(Bytes::from_owner) } ================================================ FILE: rust/mlt/src/convert/tileset.rs ================================================ use std::path::Path; use std::sync::Arc; use std::sync::atomic::{AtomicU64, Ordering}; use std::time::{Duration, Instant}; use anyhow::{Result as AnyResult, anyhow, bail}; use indicatif::{ProgressBar, ProgressStyle}; use martin_tile_utils::Format; use mbtiles::{Mbtiles, MbtilesTranscoder}; use mlt_core::encoder::EncoderConfig; use size_format::SizeFormatterSI; use super::{MbtFormat, encode_one, whole_rate_per_sec}; #[derive(Default)] struct EncodeSizes { bytes_in: AtomicU64, bytes_out: AtomicU64, } pub async fn convert_mbtiles( input: &Path, output: &Path, mbtiles_format: Option, cfg: EncoderConfig, ) -> AnyResult<()> { let src = Mbtiles::new(input)?; let mut src_conn = src.open_readonly().await?; let meta = src.get_metadata(&mut src_conn).await?; let tile_info = src .detect_format(&meta.tilejson, &mut src_conn) .await? .ok_or_else(|| anyhow!("{} appears to be empty", input.display()))?; if tile_info.format != Format::Mvt { bail!( "Expected MVT tiles, got {} in {}", tile_info.format, input.display() ); } let src_type = src.detect_type(&mut src_conn).await?; let mbt_type = mbtiles_format.map_or(src_type, Into::into); let encoding = tile_info.encoding; let count_table = match src_type.normalized_schema() { Some(schema) => schema.content_table(), None if matches!(src_type, mbtiles::MbtType::FlatWithHash) => "tiles_with_hash", None => "tiles", }; #[expect(clippy::cast_sign_loss, reason = "COUNT(*) is always non-negative")] let total: u64 = sqlx::query_scalar::<_, i64>(&format!("SELECT COUNT(*) FROM {count_table}")) .fetch_one(&mut src_conn) .await? as u64; // The transcoder opens its own connection. drop(src_conn); eprintln!("{} → {} ({mbt_type}):", input.display(), output.display()); let start = Instant::now(); let bar = ProgressBar::new(total); bar.set_style( ProgressStyle::default_bar() .template(" {bar:40.cyan/blue} {pos}/{len} tiles [{rate}, eta {eta}]") .expect("invalid bar template") .with_key("rate", whole_rate_per_sec), ); bar.enable_steady_tick(Duration::from_millis(200)); let bar_ref = bar.clone(); let sizes = Arc::new(EncodeSizes::default()); let sizes_ref = Arc::clone(&sizes); let mut transcoder = MbtilesTranscoder::new(input, output, move |data| { sizes_ref .bytes_in .fetch_add(data.len() as u64, Ordering::Relaxed); let result = encode_one(data, encoding, cfg) .map_err(|e| -> Box { e.to_string().into() }); if let Ok(ref encoded) = result { sizes_ref .bytes_out .fetch_add(encoded.len() as u64, Ordering::Relaxed); } bar_ref.inc(1); result }) .batch_size(500) .cache_max_bytes(512 * 1024 * 1024) .max_tile_track_size(1024) .copy_metadata(true) .channel_buffer(4); if mbt_type != src_type { transcoder = transcoder.dst_type(mbt_type); } let stats = transcoder.run().await?; bar.finish_and_clear(); // The transcoder copies source metadata; override `format` to MLT. let dst = Mbtiles::new(output)?; let mut dst_conn = dst.open_or_new().await?; dst.set_metadata_value(&mut dst_conn, "format", Format::Mlt.metadata_format_value()) .await?; let in_bytes = sizes.bytes_in.load(Ordering::Relaxed); let out_bytes = sizes.bytes_out.load(Ordering::Relaxed); eprintln!( " converted {} tiles ({} unique encoded, {} cache hits, {:.1}B → {:.1}B) in {:.1?}", stats.tiles_written, stats.cache_encoded, stats.cache_hits, SizeFormatterSI::new(in_bytes), SizeFormatterSI::new(out_bytes), start.elapsed(), ); Ok(()) } ================================================ FILE: rust/mlt/src/dump.rs ================================================ use std::fs; use std::path::PathBuf; use anyhow::{Result as AnyResult, bail}; use clap::Args; use mlt_core::geojson::FeatureCollection; use mlt_core::{Decoder, MltResult, Parser}; use crate::OutputFormat; use crate::ls::is_mlt_extension; #[derive(Args)] pub struct DumpArgs { /// Path to a tile file (.mlt, .mvt, .pbf) file: PathBuf, /// Output format #[arg(short, long, default_value_t, value_enum)] format: OutputFormat, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum AfterDump { KeepRaw, Decode, } pub fn dump(args: &DumpArgs, decode: AfterDump) -> AnyResult<()> { let buffer = fs::read(&args.file)?; if is_mlt_extension(&args.file) { dump_mlt(args, decode, &buffer)?; } else { dump_mvt(args, buffer)?; } Ok(()) } fn dump_mlt(args: &DumpArgs, decode: AfterDump, buffer: &[u8]) -> AnyResult<()> { let layers = Parser::default().parse_layers(buffer)?; match args.format { OutputFormat::Text => match decode { AfterDump::KeepRaw => { for (i, layer) in layers.into_iter().enumerate() { println!("=== Layer {i} ==="); println!("{layer:#?}"); } } AfterDump::Decode => { let layers = Decoder::default().decode_all(layers)?; for (i, layer) in layers.into_iter().enumerate() { println!("=== Layer {i} ==="); println!("{layer:#?}"); } } }, OutputFormat::GeoJson => { if decode == AfterDump::KeepRaw { bail!("GeoJSON output only works with `mlt decode`"); } let fc = FeatureCollection::from_layers(Decoder::default().decode_all(layers)?)?; println!("{}", serde_json::to_string_pretty(&fc)?); } } Ok(()) } fn dump_mvt(args: &DumpArgs, buffer: Vec) -> MltResult<()> { let fc = mlt_core::mvt::mvt_to_feature_collection(buffer)?; match args.format { OutputFormat::Text => { for (i, feature) in fc.features.iter().enumerate() { println!("=== Feature {i} ==="); println!("{feature:#?}"); } } OutputFormat::GeoJson => { println!("{}", serde_json::to_string_pretty(&fc)?); } } Ok(()) } ================================================ FILE: rust/mlt/src/ls.rs ================================================ use std::collections::HashSet; use std::ffi::OsStr; use std::fs; use std::io::Write as _; use std::path::{Path, PathBuf}; use std::str::FromStr as _; use std::string::ToString; use anyhow::Result as AnyResult; use clap::{Args, ValueEnum}; use flate2::Compression; use flate2::write::GzEncoder; use globset::{GlobSet, GlobSetBuilder}; use mlt_core::geojson::FeatureCollection; use mlt_core::mvt::mvt_to_feature_collection; use mlt_core::wire::StatType::{DecodedDataSize, DecodedMetaSize, FeatureCount}; use mlt_core::wire::{ Analyze as _, DictionaryType, LengthType, LogicalEncoding, OffsetType, PhysicalEncoding, StreamMeta, StreamType, }; use mlt_core::{Decoder, GeometryType, Parser}; use rayon::iter::{IntoParallelRefIterator as _, ParallelIterator as _}; use serde::Serialize; use size_format::SizeFormatterSI; use tabled::Table; use tabled::builder::Builder; use tabled::settings::object::{Cell, Columns}; use tabled::settings::span::ColumnSpan; use tabled::settings::style::HorizontalLine; use tabled::settings::{Alignment, Style}; use thousands::Separable as _; #[derive(Debug, Args)] pub struct LsArgs { /// Paths to tile files (.mlt, .mvt, .pbf) or directories #[arg(required = true)] paths: Vec, /// Filter by file extension (e.g. mlt, mvt, pbf). Can be specified multiple times. #[arg(short = 'e', long)] extension: Vec, /// Exclude paths matching the given glob (e.g. "**/fixtures/**", "**/*.pbf"). Can be specified multiple times. #[arg(short = 'E', long = "exclude")] exclude: Vec, /// Disable recursive directory traversal #[arg(long)] no_recursive: bool, /// Level of detail to show (can be specified multiple times for more details) #[arg(short, long, value_enum, default_values = ["basic", "gzip"])] details: Vec, /// Output format (table or JSON) #[arg(short, long, default_value = "table", value_enum)] format: LsFormat, /// Validate tile files against JSON validation files in the same directory (with .json extension) #[arg(long)] validate_to_json: bool, } #[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] pub enum Detail { /// Show basic statistics: file size, encoding %, layers, features Basic, /// Show all available statistics All, /// Show gzip size estimation and compression ratio #[clap(name = "gzip")] GZip, /// Show stream/encoding algorithms used (Algorithms column) Algorithms, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct LsFlags { pub gzip: bool, pub algorithms: bool, pub validate: bool, } impl From<&LsArgs> for LsFlags { fn from(args: &LsArgs) -> Self { use Detail::{Algorithms, All, GZip}; let details = args.details.as_slice(); Self { gzip: details.contains(&GZip) || details.contains(&All), algorithms: details.contains(&Algorithms) || details.contains(&All), validate: args.validate_to_json, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, ValueEnum)] pub enum LsFormat { /// Table output with aligned columns Table, /// JSON output Json, } /// Compression reduction: `(1 - compressed/original) * 100`. /// Returns 0 if `original` is 0. #[expect(clippy::cast_precision_loss)] fn percent(compressed: usize, original: usize) -> f64 { if original > 0 { (1.0 - compressed as f64 / original as f64) * 100.0 } else { 0.0 } } #[expect(clippy::cast_precision_loss)] fn percent_of(part: usize, whole: usize) -> f64 { if whole > 0 { (part as f64 / whole as f64) * 100.0 } else { 0.0 } } /// Column index for file table sorting in the UI. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FileSortColumn { File, Size, EncPct, Layers, Features, } /// Algorithm description for a file (MLT stream combo or protobuf for MVT). #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum FileAlgorithm { Mlt(StreamType, PhysicalEncoding, StatLogicalCodec), Mvt, } impl std::fmt::Display for FileAlgorithm { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Mvt => write!(f, "Protobuf"), Self::Mlt(phys_type, physical, logical) => { let phys_type = match phys_type { StreamType::Present => "Present", StreamType::Data(v) => match v { DictionaryType::None => "RawData", DictionaryType::Vertex => "Vertex", DictionaryType::Single => "Single", DictionaryType::Shared => "Shared", DictionaryType::Morton => "Morton", DictionaryType::Fsst => "Fsst", }, StreamType::Offset(v) => match v { OffsetType::Vertex => "VertexOffset", OffsetType::Index => "IndexOffset", OffsetType::String => "StringOffset", OffsetType::Key => "KeyOffset", }, StreamType::Length(v) => match v { LengthType::VarBinary => "VarBinaryLen", LengthType::Geometries => "GeomLen", LengthType::Parts => "PartsLen", LengthType::Rings => "RingsLen", LengthType::Triangles => "TrianglesLen", LengthType::Symbol => "SymbolLen", LengthType::Dictionary => "DictLen", }, }; let physical = match physical { PhysicalEncoding::None => "", PhysicalEncoding::FastPFor256 => "FastPFOR", PhysicalEncoding::VarInt => "VarInt", }; let logical = match logical { StatLogicalCodec::None => "", StatLogicalCodec::Delta => "Delta", StatLogicalCodec::DeltaRle => "DeltaRle", StatLogicalCodec::Rle => "Rle", StatLogicalCodec::ComponentwiseDelta => "CwDelta", StatLogicalCodec::Morton => "Morton", StatLogicalCodec::MortonDelta => "MortonDelta", StatLogicalCodec::MortonRle => "MortonRle", StatLogicalCodec::PseudoDecimal => "PseudoDec", }; write!(f, "{phys_type}")?; if !physical.is_empty() { write!(f, "-{physical}")?; } if !logical.is_empty() { write!(f, "-{logical}")?; } Ok(()) } } } } impl Serialize for FileAlgorithm { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_str(&self.to_string()) } } /// Dash shown when a numeric column is not applicable (e.g. MVT has no Enc %). pub const NA: &str = "—"; #[must_use] pub fn na(v: Option) -> String { v.unwrap_or_else(|| NA.to_string()) } #[derive(Debug, Clone, Default, Serialize)] pub struct MltFileInfo { pub path: String, pub size: usize, pub encoding_pct: Option, pub data_size: Option, pub meta_size: Option, pub meta_pct: Option, pub gzipped_size: Option, pub gzip_pct: Option, pub layers: usize, pub features: usize, pub streams: Option, pub algorithms: HashSet, pub geometries: HashSet, pub matches_json: Option, } impl MltFileInfo {} impl MltFileInfo { #[must_use] pub fn geometries_display(&self) -> String { geometries_display(&self.geometries) } #[must_use] pub fn algorithms_display(&self) -> String { algorithms_display(&self.algorithms) } } #[derive(serde::Serialize, Clone)] #[serde(untagged)] #[expect(clippy::large_enum_variant)] pub enum LsRow { Info { path: PathBuf, info: MltFileInfo, }, Error { path: PathBuf, size: Option, error: String, }, /// Placeholder while analysis is in progress Loading { path: PathBuf, }, } impl LsRow { /// Path for this row (file path, or path that failed/loading). #[must_use] pub fn path(&self) -> &Path { match self { Self::Info { path, .. } | Self::Error { path, .. } | Self::Loading { path } => { path.as_path() } } } } /// True if the path string contains glob metacharacters `"*?[{"`. fn has_glob_metachars(path: &Path) -> bool { let s = path.to_string_lossy(); s.contains('*') || s.contains('?') || s.contains('[') || s.contains('{') } /// Expand path arguments: if a path contains glob metacharacters, expand it to matching paths; /// otherwise use the path as-is. Directories are left as-is so `collect_tile_files` can recurse into them. fn expand_path_args(paths: &[PathBuf]) -> AnyResult> { let mut out = Vec::new(); for path in paths { if has_glob_metachars(path) { for entry in glob::glob(path.to_string_lossy().as_ref())? { out.push(entry?); } } else { out.push(path.clone()); } } Ok(out) } /// Build a `GlobSet` from patterns; returns None if patterns is empty. fn build_exclude_set(patterns: &[String]) -> AnyResult> { if patterns.is_empty() { return Ok(None); } let mut builder = GlobSetBuilder::new(); for p in patterns { builder.add(globset::Glob::new(p)?); } Ok(Some(builder.build()?)) } /// List tile files with statistics. /// Returns `true` if all files were valid, `false` if any file had an error, or no files. pub fn ls(args: &LsArgs) -> AnyResult { let flags = LsFlags::from(args); let mut all_files = Vec::new(); // Expand path arguments as globs when they contain *?[{; directories are left as-is and handled below. let expanded_paths = expand_path_args(&args.paths)?; let exclude = build_exclude_set(&args.exclude)?; for path in &expanded_paths { let files = collect_tile_files(path, args, exclude.as_ref())?; all_files.extend(files); } if all_files.is_empty() { eprintln!("No tile files found"); return Ok(false); } let base_path = if args.paths.len() == 1 && !has_glob_metachars(&args.paths[0]) { &args.paths[0] } else { Path::new(".") }; let result = analyze_tile_files(all_files.as_slice(), base_path, flags); match args.format { LsFormat::Table => print_table(&result, flags), LsFormat::Json => println!("{}", serde_json::to_string_pretty(&result)?), } Ok(result.iter().all(|r| match r { LsRow::Info { info: MltFileInfo { matches_json, .. }, .. } => matches_json.unwrap_or(true), _ => false, })) } /// Analyze tile files (MLT and MVT) and return rows (for reuse by UI). #[must_use] pub fn analyze_tile_files(paths: &[PathBuf], base_path: &Path, flags: LsFlags) -> Vec { paths .par_iter() .map(|path| match analyze_tile_file(path, base_path, flags) { Ok(info) => LsRow::Info { path: path.clone(), info, }, Err(e) => LsRow::Error { path: path.clone(), error: e.to_string(), size: fs::metadata(path) .ok() .and_then(|m| usize::try_from(m.len()).ok()), }, }) .collect() } /// Return cells for UI table display: [File, Size, Enc%, Layers, Features]. #[must_use] pub fn row_cells(row: &LsRow) -> [String; 5] { let fmt_size = |n: usize| format!("{:.1}B", SizeFormatterSI::new(n as u64)); match row { LsRow::Info { info, .. } => [ info.path.clone(), format!("{:>8}", fmt_size(info.size)), format!("{:>6}", na(info.encoding_pct.map(fmt_pct))), format!("{:>6}", info.layers), format!("{:>10}", info.features.separate_with_commas()), ], LsRow::Error { path, error: _, size, } => [ path.display().to_string(), size.map_or_else(String::new, |n| { format!("{:>8}", format!("{:.1}B", SizeFormatterSI::new(n as u64))) }), String::new(), String::new(), String::new(), ], LsRow::Loading { path } => [ path.display().to_string(), "…".to_string(), "…".to_string(), "…".to_string(), "…".to_string(), ], } } /// Path string for UI display; when `base` is given, returns path relative to base (same as Info row display). #[must_use] pub fn path_display(path: &Path, base: Option<&Path>) -> String { match base { None => path.display().to_string(), Some(b) if b.is_file() => path .file_name() .and_then(|n| n.to_str()) .unwrap_or("") .to_string(), Some(b) => path.strip_prefix(b).map_or_else( |_| path.display().to_string(), |p| p.to_string_lossy().to_string(), ), } } /// Six-column cells for UI table: [File, Size, Enc %, Layers, Features, Notes]. Uses `path_display(path, base)` for the file column. Notes column is error message for Error rows, empty otherwise. #[must_use] pub fn row_cells_6(row: &LsRow, base: Option<&Path>) -> [String; 6] { let cells5 = row_cells(row); let file_col = path_display(row.path(), base); let notes = match row { LsRow::Error { error, .. } => error.clone(), LsRow::Info { .. } | LsRow::Loading { .. } => String::new(), }; [ file_col, cells5[1].clone(), cells5[2].clone(), cells5[3].clone(), cells5[4].clone(), notes, ] } pub(crate) fn is_tile_extension(path: &Path) -> bool { matches!( path.extension().and_then(OsStr::to_str), Some("mlt" | "mvt" | "pbf") ) } pub(crate) fn is_mlt_extension(path: &Path) -> bool { matches!(path.extension().and_then(OsStr::to_str), Some("mlt")) } pub(crate) fn is_mbt_extension(path: &Path) -> bool { matches!(path.extension().and_then(OsStr::to_str), Some("mbtiles")) } fn matches_extension_filter(path: &Path, extensions: &[String]) -> bool { let ext = path .extension() .and_then(OsStr::to_str) .map(str::to_lowercase); match ext { Some(ext) => extensions .iter() .any(|e| e.trim_start_matches('.').to_lowercase() == ext), None => false, } } fn collect_tile_files( path: &Path, args: &LsArgs, exclude_set: Option<&GlobSet>, ) -> AnyResult> { let matches_ext = |p: &Path| { if args.extension.is_empty() { is_tile_extension(p) } else { matches_extension_filter(p, &args.extension) } }; let excluded = |p: &Path| exclude_set.is_some_and(|s| s.is_match(p)); let mut files = Vec::new(); if path.is_dir() { collect_from_dir( path, &mut files, !args.no_recursive, &matches_ext, exclude_set, )?; } else if path.is_file() && !excluded(path) && matches_ext(path) { files.push(path.to_path_buf()); } Ok(files) } fn collect_from_dir( dir: &Path, files: &mut Vec, recursive: bool, matches_ext: &F, exclude_set: Option<&GlobSet>, ) -> AnyResult<()> where F: Fn(&Path) -> bool, { for entry in fs::read_dir(dir)? { let path = entry?.path(); if path.is_file() { if !exclude_set.is_some_and(|s| s.is_match(&path)) && matches_ext(&path) { files.push(path); } } else if recursive && path.is_dir() && !exclude_set.is_some_and(|s| s.is_match(&path)) { collect_from_dir(&path, files, recursive, matches_ext, exclude_set)?; } } Ok(()) } pub fn analyze_tile_file(path: &Path, base_path: &Path, flags: LsFlags) -> AnyResult { let buffer = fs::read(path)?; let mut info = if is_mlt_extension(path) { analyze_mlt_buffer(&buffer, path, flags)? } else { analyze_mvt_buffer(&buffer)? }; info.path = if base_path.is_file() { path.file_name() .and_then(|n| n.to_str()) .unwrap_or("") .to_string() } else { path.strip_prefix(base_path) .unwrap_or(path) .to_string_lossy() .to_string() }; if flags.gzip { let gzip_size = estimate_gzip_size(&buffer)?; info.gzipped_size = Some(gzip_size); info.gzip_pct = Some(percent(gzip_size, buffer.len())); } Ok(info) } pub fn analyze_mlt_buffer(buffer: &[u8], path: &Path, flags: LsFlags) -> AnyResult { let layers = Parser::default().parse_layers(buffer)?; let mut stream_count = 0; let mut algorithms: HashSet = HashSet::new(); for layer in &layers { if let Some(layer01) = layer.as_layer01() { layer01.for_each_stream(&mut |stream_meta| { stream_count += 1; collect_stream_info(stream_meta, &mut algorithms); }); } } let layers = Decoder::default().decode_all(layers)?; let mut geometries = HashSet::new(); let mut feature_count = 0; let mut data_size = 0; let mut meta_size = 0; for layer in &layers { if let Some(layer01) = layer.as_layer01() { data_size += layer01.collect_statistic(DecodedDataSize); meta_size += layer01.collect_statistic(DecodedMetaSize); feature_count += layer01.collect_statistic(FeatureCount); for &geom_type in layer01.geometry_values().vector_types() { geometries.insert(geom_type); } } } let layer_count = layers.len(); let matches_json = if flags.validate { let json_path = path.with_extension("json"); if json_path.is_file() { let expected = FeatureCollection::from_str(&fs::read_to_string(&json_path)?) .map_err(|e| anyhow::anyhow!("{e}"))?; let actual = FeatureCollection::from_layers(layers)?; Some(actual.equals(&expected)?) } else { Some(false) } } else { None }; let algorithms: HashSet = algorithms .into_iter() .map(|(a, b, c)| FileAlgorithm::Mlt(a, b, c)) .collect(); Ok(MltFileInfo { size: buffer.len(), encoding_pct: Some(percent(buffer.len(), data_size + meta_size)), data_size: Some(data_size), meta_size: Some(meta_size), meta_pct: Some(percent_of(meta_size, data_size)), layers: layer_count, features: feature_count, streams: Some(stream_count), algorithms, geometries, matches_json, ..MltFileInfo::default() }) } fn analyze_mvt_buffer(buffer: &[u8]) -> AnyResult { let fc = mvt_to_feature_collection(buffer.to_vec())?; let mut layer_names = HashSet::new(); let mut geometries = HashSet::new(); for feat in &fc.features { // FIXME: we shouldn't use "magical" properties to pass values around if let Some(name) = feat.properties.get("_layer").and_then(|v| v.as_str()) { layer_names.insert(name.to_string()); } if let Ok(gt) = GeometryType::try_from(&feat.geometry) { geometries.insert(gt); } } Ok(MltFileInfo { size: buffer.len(), layers: layer_names.len(), features: fc.features.len(), algorithms: std::iter::once(FileAlgorithm::Mvt).collect(), geometries, ..MltFileInfo::default() }) } type StreamStat = (StreamType, PhysicalEncoding, StatLogicalCodec); /// Mirrors [`LogicalEncoding`] without associated metadata values. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum StatLogicalCodec { None, Delta, DeltaRle, ComponentwiseDelta, Rle, Morton, MortonDelta, MortonRle, PseudoDecimal, } impl From for StatLogicalCodec { fn from(ld: LogicalEncoding) -> Self { match ld { LogicalEncoding::None => Self::None, LogicalEncoding::Delta => Self::Delta, LogicalEncoding::DeltaRle(_) => Self::DeltaRle, LogicalEncoding::ComponentwiseDelta => Self::ComponentwiseDelta, LogicalEncoding::Rle(_) => Self::Rle, LogicalEncoding::Morton(_) => Self::Morton, LogicalEncoding::MortonDelta(_) => Self::MortonDelta, LogicalEncoding::MortonRle(_) => Self::MortonRle, LogicalEncoding::PseudoDecimal => Self::PseudoDecimal, } } } fn collect_stream_info(meta: StreamMeta, algo: &mut HashSet) { algo.insert(( meta.stream_type, meta.encoding.physical, StatLogicalCodec::from(meta.encoding.logical), )); } fn estimate_gzip_size(data: &[u8]) -> AnyResult { let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); encoder.write_all(data)?; let compressed = encoder.finish()?; Ok(compressed.len()) } fn geometries_display(geometries: &HashSet) -> String { let abbrev = |g: GeometryType| match g { GeometryType::Point => "Pt", GeometryType::LineString => "Line", GeometryType::Polygon => "Poly", GeometryType::MultiPoint => "MPt", GeometryType::MultiLineString => "MLine", GeometryType::MultiPolygon => "MPoly", }; let mut v: Vec = geometries.iter().copied().collect(); v.sort_unstable(); v.iter().map(|g| abbrev(*g)).collect::>().join(",") } fn algorithms_display(algorithms: &HashSet) -> String { let mut v: Vec<_> = algorithms.iter().map(ToString::to_string).collect(); v.sort_unstable(); v.join(",") } fn print_table(rows: &[LsRow], flags: LsFlags) { let fmt_size = |n: usize| format!("{:.1}B", SizeFormatterSI::new(n as u64)); let infos: Vec<&MltFileInfo> = rows .iter() .filter_map(|r| match r { LsRow::Info { info, .. } => Some(info), LsRow::Error { .. } | LsRow::Loading { .. } => None, }) .collect(); let has_total = infos.len() > 1; let mut error_table_rows = Vec::new(); let mut builder = Builder::default(); let mut header = vec!["File", "Size", "Enc %", "Decoded", "Meta", "Meta %"]; if flags.gzip { header.push("Gzipped"); header.push("Gz %"); } header.extend(["Layer", "Feature", "Stream", "Geometry Types"]); if flags.validate { header.push("JSON"); } if flags.algorithms { header.push("Algorithms"); } let num_cols = header.len(); builder.push_record(header); for (i, row) in rows.iter().enumerate() { match row { LsRow::Info { info, .. } => { if let Some(true) = info.matches_json && flags.validate { continue; // When validating, no need to show valid rows } let mut data_row = vec![ info.path.clone(), fmt_size(info.size), na(info.encoding_pct.map(fmt_pct)), na(info.data_size.map(fmt_size)), na(info.meta_size.map(fmt_size)), na(info.meta_pct.map(fmt_pct)), ]; if flags.gzip { data_row.push(na(info.gzipped_size.map(fmt_size))); data_row.push(na(info.gzip_pct.map(fmt_pct))); } data_row.extend([ info.layers.separate_with_commas(), info.features.separate_with_commas(), na(info.streams.map(|n| n.separate_with_commas())), info.geometries_display(), ]); if flags.validate { data_row.push(match info.matches_json { Some(true) => "✓".to_string(), Some(false) => "✗".to_string(), None => NA.to_string(), }); } if flags.algorithms { data_row.push(info.algorithms_display()); } builder.push_record(data_row); } LsRow::Error { path, error, size } => { let size_str = size.map_or_else(String::new, &fmt_size); let mut data_row = vec![ path.display().to_string(), size_str, format!("ERROR: {error}"), ]; data_row.resize(num_cols, String::new()); builder.push_record(data_row); error_table_rows.push(i + 1); } LsRow::Loading { .. } => unreachable!("Loading?"), } } if has_total { let total_size: usize = infos.iter().map(|i| i.size).sum(); let total_data: Option = infos .iter() .try_fold(0usize, |acc, i| i.data_size.map(|d| acc + d)); let total_meta: Option = infos .iter() .try_fold(0usize, |acc, i| i.meta_size.map(|m| acc + m)); let total_gzipped: usize = infos.iter().filter_map(|i| i.gzipped_size).sum(); let total_layers: usize = infos.iter().map(|i| i.layers).sum(); let total_features: usize = infos.iter().map(|i| i.features).sum(); let total_streams: Option = infos .iter() .try_fold(0usize, |acc, i| i.streams.map(|s| acc + s)); let (enc_pct, decoded, meta, meta_pct) = match (total_data, total_meta) { (Some(d), Some(m)) => ( fmt_pct(percent(total_size, d + m)), fmt_size(d), fmt_size(m), fmt_pct(percent_of(m, d)), ), _ => ( NA.to_string(), NA.to_string(), NA.to_string(), NA.to_string(), ), }; let mut row = vec![ "TOTAL".to_string(), fmt_size(total_size), enc_pct, decoded, meta, meta_pct, ]; if flags.gzip { let has_any_gzip = infos.iter().any(|i| i.gzipped_size.is_some()); let gzip_size_str = if has_any_gzip { fmt_size(total_gzipped) } else { NA.to_string() }; let gzip_pct_str = if has_any_gzip { fmt_pct(percent(total_gzipped, total_size)) } else { NA.to_string() }; row.push(gzip_size_str); row.push(gzip_pct_str); } row.extend([ total_layers.separate_with_commas(), total_features.separate_with_commas(), na(total_streams.map(|s| s.separate_with_commas())), String::new(), ]); if flags.validate { row.push(String::new()); } if flags.algorithms { row.push(String::new()); } builder.push_record(row); } let header_line = HorizontalLine::new('-').intersection('+'); let mut table = Table::from(builder); #[expect(clippy::cast_possible_wrap)] let col_span = ColumnSpan::new((num_cols - 1) as isize); for &row_idx in &error_table_rows { table.modify(Cell::new(row_idx, 1), col_span); } if has_total { let total_row = rows.len() + 1; table.with( Style::empty() .vertical('|') .horizontals([(1, header_line), (total_row, header_line)]), ); } else { table.with(Style::empty().vertical('|').horizontals([(1, header_line)])); } // File - left aligned, size..stream (9-11) right, two more left table.modify( Columns::new(1..9 + if flags.gzip { 2 } else { 0 }), Alignment::right(), ); for &row_idx in &error_table_rows { table.modify(Cell::new(row_idx, 1), Alignment::left()); } println!("{table}"); } fn fmt_pct(v: f64) -> String { if v.abs() >= 10.0 { format!("{v:.0}%") } else if v.abs() >= 1.0 { format!("{v:.1}%") } else { format!("{v:.2}%") } } ================================================ FILE: rust/mlt/src/main.rs ================================================ pub mod convert; pub mod dump; pub mod ls; pub mod ui; use std::process::exit; use anyhow::Result as AnyResult; use clap::{Parser, Subcommand, ValueEnum}; use crate::convert::{ConvertArgs, convert}; use crate::dump::{AfterDump, DumpArgs, dump}; use crate::ls::{LsArgs, ls}; use crate::ui::{UiArgs, ui}; #[hotpath::main] fn main() -> AnyResult<()> { match Cli::parse().command { Commands::Convert(args) => convert(&args)?, Commands::Dump(args) => dump(&args, AfterDump::KeepRaw)?, Commands::Decode(args) => dump(&args, AfterDump::Decode)?, Commands::Ls(args) => { if !ls(&args)? { exit(1) } } Commands::Ui(args) => ui(&args)?, } Ok(()) } #[derive(Parser)] #[command(name = "mlt", about = "MapLibre Tile format utilities")] struct Cli { #[command(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { /// Convert .mlt and .mvt tiles in a directory tree to re-encoded .mlt files Convert(ConvertArgs), /// Parse a tile file (.mlt, .mvt, .pbf) and dump raw layer data without decoding Dump(DumpArgs), /// Parse a tile file (.mlt, .mvt, .pbf), decode all layers, and dump the result Decode(DumpArgs), /// List tile files with statistics Ls(LsArgs), /// Visualize a tile file (.mlt, .mvt, .pbf) in an interactive TUI Ui(UiArgs), } #[derive(Clone, Default, ValueEnum)] enum OutputFormat { /// Human-readable text output #[default] Text, /// `GeoJSON` output #[clap(alias = "geojson")] GeoJson, } ================================================ FILE: rust/mlt/src/ui/mbt.rs ================================================ //! `MBTiles` map viewer state and tile loading. use std::collections::{HashMap, HashSet}; use std::path::PathBuf; use std::sync::mpsc::{self, TryRecvError}; use std::thread; use martin_tile_utils::{decode_gzip, decode_zstd}; use mbtiles::Mbtiles; use mlt_core::geo_types::{Coord, Geometry, Polygon}; use mlt_core::geojson::FeatureCollection; use mlt_core::mvt::mvt_to_feature_collection; use rstar::{AABB, PointDistance, RTree, RTreeObject}; use super::group_by_layer; use super::state::LayerGroup; type DecodedTile = (FeatureCollection, u32, Vec, RTree); type BestHover = Option<(f64, (u8, u32, u32), usize, usize)>; // --------------------------------------------------------------------------- // Tile loading channel types // --------------------------------------------------------------------------- pub(crate) enum TileLoadRequest { Load { z: u8, x: u32, y: u32 }, } pub(crate) struct TileLoadResult { pub z: u8, pub x: u32, pub y: u32, pub data: Result>, String>, } // --------------------------------------------------------------------------- // Geometry index entry (world coordinates) // --------------------------------------------------------------------------- /// Feature entry stored in the per-tile R-tree, using world coordinates. /// World coordinate space: `x ∈ [0, 1]` west→east, `y ∈ [0, 1]` north→south. pub(crate) struct MbtGeoEntry { pub layer: usize, pub feat: usize, pub vertices: Vec<[f64; 2]>, } impl RTreeObject for MbtGeoEntry { type Envelope = AABB<[f64; 2]>; fn envelope(&self) -> Self::Envelope { if self.vertices.is_empty() { return AABB::from_point([0.0, 0.0]); } let (ax, ay, bx, by) = self.vertices.iter().fold( ( f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY, ), |(ax, ay, bx, by), v| (ax.min(v[0]), ay.min(v[1]), bx.max(v[0]), by.max(v[1])), ); AABB::from_corners([ax, ay], [bx, by]) } } impl PointDistance for MbtGeoEntry { fn distance_2(&self, point: &[f64; 2]) -> f64 { self.vertices .iter() .map(|v| { let dx = v[0] - point[0]; let dy = v[1] - point[1]; dx * dx + dy * dy }) .fold(f64::INFINITY, f64::min) } } // --------------------------------------------------------------------------- // Tile coordinate transform (tile-local → world) // --------------------------------------------------------------------------- /// Transforms coordinates from tile-local space ([0, extent]) to world space ([0, 1]). pub(crate) struct TileTransform { n: f64, // 2^z tx: f64, // tile x index ty: f64, // tile y index ext: f64, // tile extent } impl TileTransform { pub(crate) fn new(z: u8, tile_x: u32, tile_y: u32, extent: u32) -> Self { Self { n: f64::from(1u32 << z), tx: f64::from(tile_x), ty: f64::from(tile_y), ext: f64::from(extent), } } /// Convert a tile-local coordinate to world coordinates. #[inline] pub(crate) fn to_world(&self, c: Coord) -> [f64; 2] { [ (self.tx + f64::from(c.x) / self.ext) / self.n, (self.ty + f64::from(c.y) / self.ext) / self.n, ] } /// Collect world-coordinate vertices from a polygon. pub(crate) fn poly_verts(&self, poly: &Polygon) -> Vec<[f64; 2]> { poly.exterior() .0 .iter() .copied() .chain(poly.interiors().iter().flat_map(|r| r.0.iter().copied())) .map(|c| self.to_world(c)) .collect() } /// Collect world-coordinate vertices from any geometry. pub(crate) fn geom_verts(&self, geom: &Geometry) -> Vec<[f64; 2]> { match geom { Geometry::::Point(p) => vec![self.to_world(p.0)], Geometry::::LineString(ls) => { ls.0.iter().copied().map(|c| self.to_world(c)).collect() } Geometry::::MultiPoint(mp) => mp.iter().map(|p| self.to_world(p.0)).collect(), Geometry::::Polygon(poly) => self.poly_verts(poly), Geometry::::MultiLineString(mls) => mls .iter() .flat_map(|ls| ls.0.iter().copied().map(|c| self.to_world(c))) .collect(), Geometry::::MultiPolygon(mpoly) => { mpoly.iter().flat_map(|p| self.poly_verts(p)).collect() } _ => vec![], } } } // --------------------------------------------------------------------------- // Tile cache entry // --------------------------------------------------------------------------- pub(crate) enum MbtTileData { Loading, Empty, #[allow(dead_code)] Error(String), Loaded { fc: FeatureCollection, extent: u32, layer_groups: Vec, geo_index: RTree, }, } // --------------------------------------------------------------------------- // Hover state // --------------------------------------------------------------------------- #[derive(Clone, PartialEq, Eq)] pub(crate) struct MbtHoveredInfo { pub tile: (u8, u32, u32), pub layer_idx: usize, pub feat_idx: usize, } // --------------------------------------------------------------------------- // MbtilesState // --------------------------------------------------------------------------- pub(crate) struct MbtilesState { #[allow(dead_code)] pub path: PathBuf, /// Viewport bounds in world coords: `x ∈ [0, 1]` west→east, `y ∈ [0, 1]` north→south. pub vp_x0: f64, pub vp_x1: f64, pub vp_y0: f64, pub vp_y1: f64, /// Cached tile data keyed by (z, x, y) in XYZ scheme. pub tiles: HashMap<(u8, u32, u32), MbtTileData>, /// Currently hovered feature. pub hovered: Option, /// Nominal zoom level (0 = full world width); changes by ±0.5 per scroll step. /// Kept in sync with viewport width after pan (`sync_zoom_f_from_vp`). pub zoom_f: f64, /// Last mouse cell during left-button map drag (`Drag` deltas are applied from here). pub map_drag_last: Option<(u16, u16)>, loading: HashSet<(u8, u32, u32)>, request_tx: mpsc::SyncSender, pub result_rx: mpsc::Receiver, /// Until the loader thread reports success, holds the one-shot init handshake receiver. loader_init_rx: Option>>, /// Fatal error from `MBTiles` open / connection (surfaced to the UI once via `take_loader_fatal`). loader_fatal: Option, } impl MbtilesState { pub(crate) fn new(path: PathBuf) -> Self { let (req_tx, req_rx) = mpsc::sync_channel::(200); let (res_tx, res_rx) = mpsc::sync_channel::(200); let (init_tx, init_rx) = mpsc::sync_channel::>(1); let path_clone = path.clone(); thread::spawn(move || { let rt = tokio::runtime::Builder::new_current_thread() .enable_io() .enable_time() .build() .expect("tokio runtime"); rt.block_on(async move { let mbt = match Mbtiles::new(&path_clone) { Ok(m) => m, Err(e) => { let _ = init_tx.send(Err(format!("Failed to open mbtiles: {e}"))); return; } }; let mut conn = match mbt.open_readonly().await { Ok(c) => c, Err(e) => { let _ = init_tx.send(Err(format!("MBTiles read-only connection failed: {e}"))); return; } }; if init_tx.send(Ok(())).is_err() { return; } drop(init_tx); while let Ok(TileLoadRequest::Load { z, x, y }) = req_rx.recv() { let result = mbt .get_tile(&mut conn, z, x, y) .await .map_err(|e| e.to_string()); let _ = res_tx.send(TileLoadResult { z, x, y, data: result, }); } }); }); Self { path, vp_x0: 0.0, vp_x1: 1.0, vp_y0: 0.0, vp_y1: 1.0, tiles: HashMap::new(), hovered: None, zoom_f: 0.0, map_drag_last: None, loading: HashSet::new(), request_tx: req_tx, result_rx: res_rx, loader_init_rx: Some(init_rx), loader_fatal: None, } } pub(crate) fn take_loader_fatal(&mut self) -> Option { self.loader_fatal.take() } /// Tile zoom used for loading tiles: floor of `-log2(viewport width)`. pub(crate) fn zoom_level(&self) -> u8 { let vp_w = self.vp_x1 - self.vp_x0; if vp_w <= 0.0 { return 0; } #[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let z = (-vp_w.log2()).floor().clamp(0.0, 22.0) as u8; z } /// `(z, 2^z as f64, 2^z)` for the current view tile grid. fn view_tile_scale(&self) -> (u8, f64, u32) { let z = self.zoom_level(); let n = 1u32 << z; (z, f64::from(n), n) } /// XYZ tile indices at zoom `z` containing world point `(wx, wy)` (XYZ, clamped). pub(crate) fn world_to_tile_xy(z: u8, wx: f64, wy: f64) -> (u32, u32) { let n = 1u32 << z; let nf = f64::from(n); #[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let tx = (wx * nf).max(0.0).floor() as u32; #[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let ty = (wy * nf).max(0.0).floor() as u32; let tx = tx.min(n.saturating_sub(1)); let ty = ty.min(n.saturating_sub(1)); (tx, ty) } /// Map-area fractions `rx, ry` (0 = left/top of map widget) to world coordinates. pub(crate) fn viewport_world_at_fracs(&self, rx: f64, ry: f64) -> (f64, f64) { let wx = self.vp_x0 + rx * (self.vp_x1 - self.vp_x0); let wy = self.vp_y0 + ry * (self.vp_y1 - self.vp_y0); (wx, wy) } fn sync_zoom_f_from_vp(&mut self) { let vp_w = self.vp_x1 - self.vp_x0; if vp_w > 0.0 { self.zoom_f = (-vp_w.log2()).clamp(0.0, 22.0); } } /// XYZ tile indices visible in the current viewport at the current zoom. pub(crate) fn visible_tiles(&self) -> Vec<(u8, u32, u32)> { let (z, nf, n) = self.view_tile_scale(); #[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let x_min = (self.vp_x0 * nf).max(0.0).floor() as u32; #[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let x_max = ((self.vp_x1 * nf).ceil() as u32) .saturating_sub(1) .min(n.saturating_sub(1)); #[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let y_min = (self.vp_y0 * nf).max(0.0).floor() as u32; #[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let y_max = ((self.vp_y1 * nf).ceil() as u32) .saturating_sub(1) .min(n.saturating_sub(1)); let mut tiles = Vec::new(); for ty in y_min..=y_max { for tx in x_min..=x_max { tiles.push((z, tx, ty)); } } tiles } /// XYZ tile (at current tile zoom) that contains the viewport center. pub(crate) fn center_tile_xyz(&self) -> (u8, u32, u32) { let z = self.zoom_level(); let wx = f64::midpoint(self.vp_x0, self.vp_x1); let wy = f64::midpoint(self.vp_y0, self.vp_y1); let (tx, ty) = Self::world_to_tile_xy(z, wx, wy); (z, tx, ty) } /// Fit the viewport exactly to XYZ tile `(z, tx, ty)` and sync `zoom_f`. pub(crate) fn set_viewport_to_tile(&mut self, z: u8, tx: u32, ty: u32) -> Result<(), String> { let n = 1u32 .checked_shl(u32::from(z)) .ok_or_else(|| format!("zoom z={z} is too large"))?; if tx >= n || ty >= n { return Err(format!( "tile index out of range for z={z}: x={tx} y={ty} (max {})", n.saturating_sub(1) )); } let nf = f64::from(n); self.vp_x0 = f64::from(tx) / nf; self.vp_x1 = f64::from(tx + 1) / nf; self.vp_y0 = f64::from(ty) / nf; self.vp_y1 = f64::from(ty + 1) / nf; self.sync_zoom_f_from_vp(); Ok(()) } /// Enqueue a tile for loading if not already cached or loading. pub(crate) fn request_tile(&mut self, z: u8, x: u32, y: u32) { let key = (z, x, y); if !self.tiles.contains_key(&key) && !self.loading.contains(&key) { self.loading.insert(key); self.tiles.insert(key, MbtTileData::Loading); if self .request_tx .try_send(TileLoadRequest::Load { z, x, y }) .is_err() { self.loading.remove(&key); self.tiles.insert( key, MbtTileData::Error( "Could not enqueue tile load (channel full or loader stopped)".into(), ), ); } } } /// Request this tile and every XYZ ancestor down to z=0 (for overzoom fallbacks). pub(crate) fn request_tile_with_ancestors(&mut self, z: u8, x: u32, y: u32) { let mut cz = z; let mut cx = x; let mut cy = y; loop { self.request_tile(cz, cx, cy); if cz == 0 { break; } cz -= 1; cx >>= 1; cy >>= 1; } } /// First loaded ancestor for overzoom (`k` levels up: tile `(x>>k, y>>k)` at `z-k`), if any. pub(crate) fn find_overzoom_source(&self, z: u8, tx: u32, ty: u32) -> Option<(u8, u32, u32)> { for k in 1..=z { let pz = z - k; let shift = u32::from(k); let px = tx >> shift; let py = ty >> shift; let key = (pz, px, py); if matches!(self.tiles.get(&key), Some(MbtTileData::Loaded { .. })) { return Some(key); } } None } /// Tile to use for data at `(z,tx,ty)`: native if loaded, otherwise first loaded ancestor. pub(crate) fn effective_tile_key(&self, z: u8, tx: u32, ty: u32) -> Option<(u8, u32, u32)> { let key = (z, tx, ty); match self.tiles.get(&key) { Some(MbtTileData::Loaded { .. }) => Some(key), _ => self.find_overzoom_source(z, tx, ty), } } /// Drain incoming results and update the tile cache. Returns true if any tiles changed. pub(crate) fn process_results(&mut self) -> bool { let mut changed = false; if let Some(rx) = self.loader_init_rx.take() { match rx.try_recv() { Ok(Ok(())) => { // Init succeeded; drop the receiver so we do not treat later disconnect as init failure. } Ok(Err(msg)) => { self.loader_fatal = Some(msg); changed = true; } Err(TryRecvError::Empty) => { self.loader_init_rx = Some(rx); } Err(TryRecvError::Disconnected) => { if self.loader_fatal.is_none() { self.loader_fatal = Some("MBTiles loader thread exited during initialization".into()); } changed = true; } } } while let Ok(res) = self.result_rx.try_recv() { let key = (res.z, res.x, res.y); self.loading.remove(&key); let entry = match res.data { Err(e) => MbtTileData::Error(e), Ok(None) => MbtTileData::Empty, Ok(Some(raw)) => match decode_and_parse(res.z, res.x, res.y, raw) { Ok(Some((fc, extent, layer_groups, geo_index))) => MbtTileData::Loaded { fc, extent, layer_groups, geo_index, }, Ok(None) => MbtTileData::Empty, Err(e) => MbtTileData::Error(e.to_string()), }, }; self.tiles.insert(key, entry); changed = true; } changed } /// Zoom in/out by half a zoom level around `(wx, wy)` (scroll wheel). pub(crate) fn zoom_wheel_at(&mut self, wx: f64, wy: f64, zoom_in: bool) { let dz = if zoom_in { 0.5 } else { -0.5 }; let new_z = (self.zoom_f + dz).clamp(0.0, 22.0); if (new_z - self.zoom_f).abs() < 1e-12 { return; } let scale = 2_f64.powf(-(new_z - self.zoom_f)); if !self.zoom_viewport_at(wx, wy, scale) { return; } self.sync_zoom_f_from_vp(); } /// Scale the viewport around `(wx, wy)` by `scale` on both axes (`scale` \< 1 zooms in). fn zoom_viewport_at(&mut self, wx: f64, wy: f64, scale: f64) -> bool { let x0 = wx + (self.vp_x0 - wx) * scale; let x1 = wx + (self.vp_x1 - wx) * scale; let y0 = wy + (self.vp_y0 - wy) * scale; let y1 = wy + (self.vp_y1 - wy) * scale; let min_size = 2_f64.powf(-22.0); if x1 - x0 < min_size || y1 - y0 < min_size { return false; } self.vp_x0 = x0; self.vp_x1 = x1; self.vp_y0 = y0; self.vp_y1 = y1; true } /// Pan the viewport by mouse delta in terminal cells (`d_col`/`d_row` = current − previous). pub(crate) fn pan_by_pixels(&mut self, area_w: u16, area_h: u16, d_col: i32, d_row: i32) { if area_w == 0 || area_h == 0 { return; } let wf = f64::from(area_w); let hf = f64::from(area_h); let vp_w = self.vp_x1 - self.vp_x0; let vp_h = self.vp_y1 - self.vp_y0; let dx = f64::from(d_col) / wf * vp_w; let dy = f64::from(d_row) / hf * vp_h; self.vp_x0 -= dx; self.vp_x1 -= dx; self.vp_y0 -= dy; self.vp_y1 -= dy; self.sync_zoom_f_from_vp(); } /// Find the nearest feature to world point (wx, wy) among visible cells (native or overzoom). pub(crate) fn find_hovered(&mut self, wx: f64, wy: f64) { let z = self.zoom_level(); let threshold = (self.vp_x1 - self.vp_x0) * 0.02; let thresh_sq = threshold * threshold; let pt = [wx, wy]; let mut best: BestHover = None; let mut seen_src: HashSet<(u8, u32, u32)> = HashSet::new(); let visible = self.visible_tiles(); for (tz, tx, ty) in visible { if tz != z { continue; } let Some(src_key) = self.effective_tile_key(tz, tx, ty) else { continue; }; if !seen_src.insert(src_key) { continue; } let Some(MbtTileData::Loaded { geo_index, .. }) = self.tiles.get(&src_key) else { continue; }; for e in geo_index.nearest_neighbor_iter(&pt) { let d = e.distance_2(&pt); if d > thresh_sq { break; } if best.is_none_or(|(bd, ..)| d < bd) { best = Some((d, src_key, e.layer, e.feat)); } } } let new_hov = best.map(|(_, tile, layer, feat)| MbtHoveredInfo { tile, layer_idx: layer, feat_idx: feat, }); self.hovered = new_hov; } /// Keys we keep when pruning: visible tiles (with a 1-tile margin) plus every ancestor chain. fn keep_tile_keys(&self) -> HashSet<(u8, u32, u32)> { let z = self.zoom_level(); let Some(n) = 1u32.checked_shl(u32::from(z)) else { return HashSet::new(); }; let mut out = HashSet::new(); for (_zv, tx, ty) in self.visible_tiles() { for dx in -1..=1 { for dy in -1..=1 { #[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)] let nx = (i64::from(tx) + i64::from(dx)) .clamp(0, i64::from(n.saturating_sub(1))) as u32; #[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)] let ny = (i64::from(ty) + i64::from(dy)) .clamp(0, i64::from(n.saturating_sub(1))) as u32; let mut cz = z; let mut cx = nx; let mut cy = ny; loop { out.insert((cz, cx, cy)); if cz == 0 { break; } cz -= 1; cx >>= 1; cy >>= 1; } } } } out } /// Drop cached tiles far from the viewport so panning does not grow memory without bound. pub(crate) fn prune_tile_cache_if_needed(&mut self) { const MAX: usize = 256; if self.tiles.len() <= MAX { return; } let keep = self.keep_tile_keys(); let stale: Vec<(u8, u32, u32)> = self .tiles .keys() .filter(|k| !keep.contains(k)) .copied() .collect(); let stale_set: HashSet<_> = stale.iter().copied().collect(); if self .hovered .as_ref() .is_some_and(|h| stale_set.contains(&h.tile)) { self.hovered = None; } for k in stale { self.loading.remove(&k); self.tiles.remove(&k); } } } // --------------------------------------------------------------------------- // Tile decode & parse helpers // --------------------------------------------------------------------------- fn decode_and_parse(z: u8, tx: u32, ty: u32, raw: Vec) -> anyhow::Result> { let buf = decompress(raw)?; if buf.is_empty() { return Ok(None); } let fc = mvt_to_feature_collection(buf)?; if fc.features.is_empty() { return Ok(None); } let extent = fc .features .first() .and_then(|f| f.properties.get("_extent")) .and_then(serde_json::Value::as_u64) .map_or(4096, |v| u32::try_from(v).unwrap_or(4096)); let layer_groups = group_by_layer(&fc); let geo_index = build_world_geo_index(z, tx, ty, &fc, &layer_groups, extent); Ok(Some((fc, extent, layer_groups, geo_index))) } fn decompress(raw: Vec) -> anyhow::Result> { if raw.len() >= 2 && raw[0] == 0x1f && raw[1] == 0x8b { Ok(decode_gzip(&raw)?) } else if raw.len() >= 4 && raw[0] == 0x28 && raw[1] == 0xb5 && raw[2] == 0x2f && raw[3] == 0xfd { Ok(decode_zstd(&raw)?) } else { Ok(raw) } } fn build_world_geo_index( z: u8, tx: u32, ty: u32, fc: &FeatureCollection, layer_groups: &[LayerGroup], extent: u32, ) -> RTree { let transform = TileTransform::new(z, tx, ty, extent); let mut entries = Vec::new(); for (li, group) in layer_groups.iter().enumerate() { for (fi, &gi) in group.feature_indices.iter().enumerate() { let geom = &fc.features[gi].geometry; let vertices = transform.geom_verts(geom); if !vertices.is_empty() { entries.push(MbtGeoEntry { layer: li, feat: fi, vertices, }); } } } RTree::bulk_load(entries) } ================================================ FILE: rust/mlt/src/ui/mod.rs ================================================ //! TUI visualizer for MLT files using ratatui pub(crate) mod mbt; mod rendering; mod state; use std::collections::HashSet; use std::fs::canonicalize; use std::io::Write as _; use std::path::{Path, PathBuf}; use std::sync::mpsc; use std::time::{Duration, Instant}; use std::{fs, thread}; use anyhow::bail; use clap::Args; use crossterm::event::{ self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, KeyModifiers, MouseButton, MouseEventKind, }; use crossterm::execute; use mlt_core::geo_types::{Coord, Geometry, Polygon}; use mlt_core::geojson::FeatureCollection; use mlt_core::mvt::mvt_to_feature_collection; use mlt_core::{Decoder, GeometryType, Parser}; use ratatui::layout::{Constraint, Direction, Layout, Margin, Rect}; use ratatui::style::{Color, Modifier, Style}; use ratatui::text::{Line, Span}; use ratatui::widgets::{Block, Borders}; use rstar::{AABB, PointDistance, RTreeObject}; use crate::ls::{ FileAlgorithm, FileSortColumn, LsFlags, LsRow, analyze_tile_files, is_mbt_extension, is_mlt_extension, is_tile_extension, }; use crate::ui::mbt::MbtilesState; use crate::ui::rendering::files::{ render_file_browser, render_file_filter_panel, render_file_info_panel, render_tile_preview_panel, }; use crate::ui::rendering::help::{render_error_popup, render_help_overlay}; use crate::ui::rendering::layers::{ render_mbtiles_hover_panel, render_properties_panel, render_tree_panel, }; use crate::ui::rendering::map::{render_map_panel, render_mbtiles_map_panel}; use crate::ui::state::{App, HoveredInfo, LayerGroup, ResizeHandle, TreeItem, ViewMode}; pub const CLR_POINT: Color = Color::Magenta; pub const CLR_MULTI_POINT: Color = Color::LightMagenta; pub const CLR_LINE: Color = Color::Cyan; pub const CLR_MULTI_LINE: Color = Color::LightCyan; pub const CLR_POLYGON: Color = Color::Blue; pub const CLR_MULTI_POLYGON: Color = Color::LightBlue; pub const CLR_INNER_RING: Color = Color::Red; pub const CLR_BAD_WINDING: Color = Color::LightRed; pub const CLR_EXTENT: Color = Color::DarkGray; pub const CLR_SELECTED: Color = Color::Yellow; pub const CLR_HOVERED: Color = Color::White; pub const CLR_HOVERED_TREE: Color = Color::LightGreen; pub const CLR_DIMMED: Color = Color::DarkGray; pub const CLR_INNER_RING_SEL: Color = Color::Rgb(255, 150, 120); pub const CLR_LABEL: Color = Color::Cyan; pub const CLR_HINT: Color = Color::DarkGray; pub const STYLE_SELECTED: Style = Style::new().fg(CLR_SELECTED).add_modifier(Modifier::BOLD); pub const STYLE_LABEL: Style = Style::new().fg(CLR_LABEL); pub const STYLE_BOLD: Style = Style::new().add_modifier(Modifier::BOLD); /// Throttle hover-driven redraws so mouse move over map doesn't flood the loop const HOVER_REDRAW_THROTTLE: Duration = Duration::from_millis(32); #[derive(Args)] pub struct UiArgs { /// Path to a tile file (`.mlt`, `.mvt`, `.pbf`, `.mbtiles`) or directory path: PathBuf, /// Start `MBTiles` map centered on this XYZ tile (`z/x/y`, e.g. `6/32/21`). `MBTiles` only. #[arg(long = "center-tile", value_name = "Z/X/Y")] center_tile: Option, } pub fn ui(args: &UiArgs) -> anyhow::Result<()> { if args.center_tile.is_some() && !is_mbt_extension(&args.path) { bail!("--center-tile is only supported when opening an .mbtiles file"); } let app = if is_mbt_extension(&args.path) { let mut mbt = MbtilesState::new(args.path.clone()); if let Some(ref s) = args.center_tile { let (z, x, y) = parse_center_tile_xyz(s)?; mbt.set_viewport_to_tile(z, x, y) .map_err(|e| anyhow::anyhow!(e))?; } App::new_mbtiles(mbt, args.path.clone()) } else if args.path.is_dir() { let paths = find_tile_files(&args.path)?; if paths.is_empty() { bail!( "No tile files found in {}", canonicalize(&args.path)?.display() ); } let base = args.path.clone(); let files: Vec = paths .iter() .map(|p| LsRow::Loading { path: p.clone() }) .collect(); let (tx, rx) = mpsc::channel(); thread::spawn(move || { let _ = tx.send(analyze_tile_files( &paths, &base, LsFlags { gzip: true, algorithms: true, validate: false, }, )); }); App::new_file_browser(files, Some(rx), args.path.clone()) } else if args.path.is_file() { App::new_single_file(load_fc(&args.path)?, Some(args.path.clone())) } else { bail!("Path is not a file or directory"); }; run_app(app) } fn parse_center_tile_xyz(spec: &str) -> anyhow::Result<(u8, u32, u32)> { let parts: Vec<&str> = spec .split('/') .map(str::trim) .filter(|p| !p.is_empty()) .collect(); if parts.len() != 3 { bail!("--center-tile must be z/x/y (three integers), got {spec:?}"); } let zoom: u8 = parts[0] .parse() .map_err(|_| anyhow::anyhow!("invalid zoom z in --center-tile: {:?}", parts[0]))?; let tile_x: u32 = parts[1] .parse() .map_err(|_| anyhow::anyhow!("invalid tile x in --center-tile: {:?}", parts[1]))?; let tile_y: u32 = parts[2] .parse() .map_err(|_| anyhow::anyhow!("invalid tile y in --center-tile: {:?}", parts[2]))?; let grid_n = 1u32 .checked_shl(u32::from(zoom)) .ok_or_else(|| anyhow::anyhow!("zoom z={zoom} is too large"))?; if tile_x >= grid_n || tile_y >= grid_n { bail!( "tile x/y must be < 2^z for z={zoom} (max index {})", grid_n.saturating_sub(1) ); } Ok((zoom, tile_x, tile_y)) } // --- Data loading --- fn load_fc(path: &Path) -> anyhow::Result { let buf = fs::read(path)?; if is_mlt_extension(path) { let layers = Decoder::default().decode_all(Parser::default().parse_layers(&buf)?)?; Ok(FeatureCollection::from_layers(layers)?) } else { Ok(mvt_to_feature_collection(buf)?) } } fn extent_from_fc(fc: &FeatureCollection) -> u32 { fc.features .first() .and_then(|f| { f.properties .get("_extent") .and_then(serde_json::Value::as_u64) }) .map_or(4096, |v| u32::try_from(v).expect("_extent is valid u32")) } fn refresh_tile_preview(app: &mut App) { let path = if let Some(r) = app.get_selected_file() { r.path().to_path_buf() } else { app.preview_tile_path = None; app.preview_fc = None; app.preview_load_requested = None; return; }; if !is_tile_extension(&path) { app.preview_tile_path = None; app.preview_fc = None; app.preview_load_requested = None; } } fn group_by_layer(fc: &FeatureCollection) -> Vec { let mut groups: Vec = Vec::new(); for (i, f) in fc.features.iter().enumerate() { let name = f .properties .get("_layer") .and_then(|v| v.as_str()) .unwrap_or("unknown"); let extent = f .properties .get("_extent") .and_then(serde_json::Value::as_u64) .map_or(4096, |v| u32::try_from(v).expect("_extent is valid u32")); if let Some(g) = groups.iter_mut().find(|g| g.name == name) { g.feature_indices.push(i); } else { groups.push(LayerGroup::new(name.to_string(), extent, vec![i])); } } groups } fn auto_expand(groups: &[LayerGroup]) -> Vec { if groups.len() == 1 { vec![true] } else { vec![false; groups.len()] } } fn find_tile_files(dir: &Path) -> anyhow::Result> { fn visit(dir: &Path, out: &mut Vec) -> anyhow::Result<()> { if dir.is_dir() { for entry in fs::read_dir(dir)? { let p = entry?.path(); if p.is_dir() { visit(&p, out)?; } else if is_tile_extension(&p) { out.push(p); } } } Ok(()) } let mut files = Vec::new(); visit(dir, &mut files)?; files.sort_unstable(); Ok(files) } // --- Hit testing --- fn point_in_rect(col: u16, row: u16, r: Rect) -> bool { col >= r.x && col < r.x + r.width && row >= r.y && row < r.y + r.height } fn click_row_in_area(col: u16, row: u16, area: Rect, scroll: usize) -> Option { let top = area.y + 1; let bot = area.y + area.height.saturating_sub(1); (col >= area.x && col < area.x + area.width && row >= top && row < bot) .then(|| (row - top) as usize + scroll) } const HIGHLIGHT_SYMBOL_WIDTH: u16 = 3; const COLUMN_SPACING: u16 = 1; /// Table has 6 columns: File, Size, Enc %, Layers, Features, Notes. Notes is not sortable. fn file_header_click_column( area: Rect, widths: &[Constraint; 6], col: u16, row: u16, ) -> Option { if row != area.y + 1 { return None; } let inner = area.inner(Margin { vertical: 1, horizontal: 1, }); let fixed: u16 = widths .iter() .map(|c| match c { Constraint::Length(l) => *l, _ => 0, }) .sum(); let remaining = inner .width .saturating_sub(HIGHLIGHT_SYMBOL_WIDTH + fixed + 5 * COLUMN_SPACING); let mut resolved = [0u16; 6]; for (i, c) in widths.iter().enumerate() { resolved[i] = match c { Constraint::Length(l) => *l, Constraint::Min(_) => remaining, _ => 0, }; } let cols = [ FileSortColumn::File, FileSortColumn::Size, FileSortColumn::EncPct, FileSortColumn::Layers, FileSortColumn::Features, ]; let mut x = inner.x + HIGHLIGHT_SYMBOL_WIDTH; for (i, &w) in resolved.iter().enumerate() { let end = if i == resolved.len() - 1 { inner.x + inner.width } else { x + w }; if col >= x && col < end { return cols.get(i).copied(); } x = end + COLUMN_SPACING; } None } fn block_with_title(title: impl Into>) -> Block<'static> { Block::default().borders(Borders::ALL).title(title) } /// Area where the map canvas actually draws (inside borders + title). /// Must stay in sync with `render_map_panel` and `render_mbtiles_map_panel`, which both use /// `block_with_title`. fn map_canvas_area(outer: Rect) -> Rect { block_with_title(" ").inner(outer) } /// Helper to build `Span::styled(format!("{name}: "), STYLE_LABEL)` + raw value. fn stat_line(name: &str, val: &dyn std::fmt::Display) -> Line<'static> { Line::from(vec![ Span::styled(format!("{name}: "), STYLE_LABEL), Span::raw(val.to_string()), ]) } const DIVIDER_GRAB: u16 = 2; fn divider_hit( col: u16, row: u16, left: Rect, tree: Rect, geom: Option, ) -> Option { let horiz_hit = |dy: u16| { row >= dy.saturating_sub(DIVIDER_GRAB) && row < dy.saturating_add(DIVIDER_GRAB) && col >= left.x && col < left.x + left.width }; // Check horizontal dividers (most specific first) if geom.is_some_and(|g| horiz_hit(g.y)) { return Some(ResizeHandle::PropertiesGeometry); } if horiz_hit(tree.y + tree.height) { return Some(ResizeHandle::FeaturesProperties); } // Check vertical divider (between left panel and map) let dx = left.x + left.width; if col >= dx.saturating_sub(DIVIDER_GRAB) && col < dx.saturating_add(DIVIDER_GRAB) && row >= left.y && row < left.y + left.height { return Some(ResizeHandle::LeftRight); } None } // --- App loop --- /// OSC 22: set mouse pointer shape fn set_pointer_cursor(pointer: bool) { let seq: &[u8] = if pointer { b"\x1b]22;default\x1b\\" } else { b"\x1b]22;\x1b\\" }; let _ = std::io::stdout().write_all(seq); let _ = std::io::stdout().flush(); } fn run_app(mut app: App) -> anyhow::Result<()> { let mut terminal = ratatui::init(); set_pointer_cursor(true); let result = (|| { execute!(terminal.backend_mut(), EnableMouseCapture)?; run_app_loop(&mut terminal, &mut app) })(); let _ = execute!(terminal.backend_mut(), DisableMouseCapture); set_pointer_cursor(false); ratatui::restore(); result } /// Compute percentage position (clamped to 10..=90) for drag resizing. fn pct_at(pos: u16, origin: u16, span: u16) -> u16 { #[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)] let pct = (f32::from(pos.saturating_sub(origin)) / f32::from(span.max(1)) * 100.0).round() as u16; pct.clamp(10, 90) } const FILE_RIGHT_PANEL_MIN: u16 = 20; fn file_browser_preview_pct_clamped(pct: u16, filter_pct: u16) -> u16 { pct.clamp( FILE_RIGHT_PANEL_MIN, 100u16 .saturating_sub(FILE_RIGHT_PANEL_MIN) .saturating_sub(filter_pct), ) } fn file_browser_filter_pct_clamped(pct: u16, preview_pct: u16) -> u16 { pct.clamp( FILE_RIGHT_PANEL_MIN, 100u16 .saturating_sub(FILE_RIGHT_PANEL_MIN) .saturating_sub(preview_pct), ) } fn run_app_loop(terminal: &mut ratatui::DefaultTerminal, app: &mut App) -> anyhow::Result<()> { let mut map_area: Option = None; let mut tree_area: Option = None; let mut props_area: Option = None; let mut geom_area: Option = None; let mut left_area: Option = None; let mut filter_area: Option = None; let mut info_area: Option = None; let mut preview_area: Option = None; let mut right_col: Option = None; let mut file_left: Option = None; let mut last_tree_click: Option<(Instant, usize)> = None; let mut last_file_click: Option<(Instant, usize)> = None; let mut last_hover_redraw: Option = None; loop { // Process incoming mbtiles tile results and request visible tiles. if app.mode == ViewMode::MbtilesMap && let Some(ref mut mbt) = app.mbt_state { if mbt.process_results() { app.needs_redraw = true; } if let Some(msg) = mbt.take_loader_fatal() { let title = app .current_file .as_ref() .map_or_else(|| "MBTiles".to_string(), |p| p.display().to_string()); app.error_popup = Some((title, msg)); app.needs_redraw = true; } let visible = mbt.visible_tiles(); for (z, x, y) in visible { mbt.request_tile_with_ancestors(z, x, y); } mbt.prune_tile_cache_if_needed(); } if let Some(rows) = app.analysis_rx.as_ref().and_then(|rx| rx.try_recv().ok()) { if rows.len() == app.files.len() { for (i, row) in rows.into_iter().enumerate() { if let Some(e) = app.files.get_mut(i) { *e = row; } } } app.analysis_rx = None; app.rebuild_filtered_files(); } if let Some((path, result)) = app.preview_rx.as_mut().and_then(|rx| rx.try_recv().ok()) { app.preview_rx = None; app.preview_load_requested = None; if app.get_selected_file().map(LsRow::path) == Some(path.as_path()) { if let Ok((fc, ext)) = result { app.preview_tile_path = Some(path); app.preview_fc = Some(fc); app.preview_extent = ext; } else { app.preview_tile_path = Some(path); app.preview_fc = None; } } app.invalidate(); } if app.mode == ViewMode::FileBrowser && let Some(selected) = app.get_selected_file() { let path = selected.path().to_path_buf(); if is_tile_extension(&path) && (app.preview_tile_path.as_ref() != Some(&path) || app.preview_fc.is_none()) && app.preview_load_requested.as_ref() != Some(&path) { let (tx, rx) = mpsc::channel(); app.preview_rx = Some(rx); app.preview_load_requested = Some(path.clone()); let path_spawn = path.clone(); thread::spawn(move || { let result = load_fc(&path_spawn) .map(|fc| { let ext = extent_from_fc(&fc); (fc, ext) }) .map_err(|_| ()); let _ = tx.send((path_spawn, result)); }); } } if app.needs_redraw { app.needs_redraw = false; terminal.draw(|f| { match app.mode { ViewMode::FileBrowser => { refresh_tile_preview(app); let cols = Layout::default() .direction(Direction::Horizontal) .constraints([ Constraint::Percentage(app.file_left_pct), Constraint::Percentage(100u16.saturating_sub(app.file_left_pct)), ]) .split(f.area()); let right_pct = file_browser_preview_pct_clamped( app.file_preview_pct, app.file_filter_pct, ); let filter_pct = file_browser_filter_pct_clamped(app.file_filter_pct, right_pct); app.file_preview_pct = right_pct; app.file_filter_pct = filter_pct; let right = Layout::default() .direction(Direction::Vertical) .constraints([ Constraint::Percentage(right_pct), Constraint::Percentage(filter_pct), Constraint::Percentage( 100u16.saturating_sub(right_pct).saturating_sub(filter_pct), ), ]) .split(cols[1]); render_file_browser(f, cols[0], app); render_tile_preview_panel(f, right[0], app); render_file_filter_panel(f, right[1], app); render_file_info_panel(f, right[2], app); file_left = Some(cols[0]); right_col = Some(cols[1]); preview_area = Some(right[0]); filter_area = Some(right[1]); info_area = Some(right[2]); } ViewMode::LayerOverview => { let cols = Layout::default() .direction(Direction::Horizontal) .constraints([ Constraint::Percentage(app.left_pct), Constraint::Percentage(100u16.saturating_sub(app.left_pct)), ]) .split(f.area()); let left = Layout::default() .direction(Direction::Vertical) .constraints([ Constraint::Percentage(app.features_pct), Constraint::Percentage(100u16.saturating_sub(app.features_pct)), ]) .split(cols[0]); render_tree_panel(f, left[0], app); let ga = render_properties_panel(f, left[1], app); render_map_panel(f, cols[1], app); tree_area = Some(left[0]); props_area = Some(left[1]); geom_area = Some(ga); left_area = Some(cols[0]); map_area = Some(map_canvas_area(cols[1])); } ViewMode::MbtilesMap => { let cols = Layout::default() .direction(Direction::Horizontal) .constraints([ Constraint::Percentage(app.left_pct), Constraint::Percentage(100u16.saturating_sub(app.left_pct)), ]) .split(f.area()); render_mbtiles_hover_panel(f, cols[0], app); render_mbtiles_map_panel(f, cols[1], app); left_area = Some(cols[0]); map_area = Some(map_canvas_area(cols[1])); } } if app.error_popup.is_some() { render_error_popup(f, app); } else if app.show_help { render_help_overlay(f, app); } })?; } if event::poll(Duration::from_millis(16))? { match event::read()? { Event::Key(key) if key.kind == KeyEventKind::Press => { if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('c') { break; } if app.error_popup.is_some() { app.error_popup = None; app.invalidate(); continue; } if app.show_help { match key.code { KeyCode::Up | KeyCode::Char('k') => { app.help_scroll = app.help_scroll.saturating_sub(1); } KeyCode::Down | KeyCode::Char('j') => { app.help_scroll = app.help_scroll.saturating_add(1); } KeyCode::PageUp => { app.help_scroll = app.help_scroll.saturating_sub(10); } KeyCode::PageDown => { app.help_scroll = app.help_scroll.saturating_add(10); } KeyCode::Home => app.help_scroll = 0, KeyCode::End => app.help_scroll = u16::MAX, _ => app.show_help = false, } app.invalidate(); continue; } match key.code { KeyCode::Char('q') => break, KeyCode::Esc if app.handle_escape() => break, KeyCode::Char('?') | KeyCode::F(1) => app.open_help(), KeyCode::Char('h') if !key.modifiers.contains(KeyModifiers::CONTROL) => { app.open_help(); } KeyCode::Enter => app.handle_enter(), KeyCode::Char('+' | '=') | KeyCode::Right => app.handle_plus(), KeyCode::Char('-') => app.handle_minus(), KeyCode::Char('*') => app.handle_star(), KeyCode::Up | KeyCode::Char('k') => app.move_up_by(1), KeyCode::Down | KeyCode::Char('j') => app.move_down_by(1), KeyCode::Left => app.handle_left_arrow(), KeyCode::PageUp => { app.move_up_by(app.page_size().saturating_sub(1).max(1)); } KeyCode::PageDown => { app.move_down_by(app.page_size().saturating_sub(1).max(1)); } KeyCode::Home => app.move_to_start(), KeyCode::End => app.move_to_end(), KeyCode::Char('h') if key.modifiers.contains(KeyModifiers::CONTROL) => { app.left_pct = app.left_pct.saturating_sub(5).max(10); app.invalidate(); } KeyCode::Char('l') if key.modifiers.contains(KeyModifiers::CONTROL) => { app.left_pct = (app.left_pct + 5).min(90); app.invalidate(); } KeyCode::Char('J') if key.modifiers.contains(KeyModifiers::SHIFT) => { app.features_pct = app.features_pct.saturating_sub(5).max(10); app.invalidate(); } KeyCode::Char('K') if key.modifiers.contains(KeyModifiers::SHIFT) => { app.features_pct = (app.features_pct + 5).min(90); app.invalidate(); } _ => {} } } Event::Mouse(mouse) if app.error_popup.is_some() => { if matches!(mouse.kind, MouseEventKind::Down(_)) { app.error_popup = None; app.invalidate(); } } Event::Mouse(mouse) if app.show_help => match mouse.kind { MouseEventKind::ScrollUp => { app.help_scroll = app.help_scroll.saturating_sub(1); app.invalidate(); } MouseEventKind::ScrollDown => { app.help_scroll = app.help_scroll.saturating_add(1); app.invalidate(); } _ => {} }, Event::Mouse(mouse) => match mouse.kind { MouseEventKind::Up(_) => { if let Some(ref mut mbt) = app.mbt_state { mbt.map_drag_last = None; } if app.resizing.take().is_some() { app.invalidate(); } } MouseEventKind::Moved | MouseEventKind::Drag(_) => { if let Some(handle) = app.resizing { let area = terminal.get_frame().area(); let la = left_area.unwrap_or_default(); match handle { ResizeHandle::LeftRight => { app.left_pct = pct_at(mouse.column, area.x, area.width); } ResizeHandle::FeaturesProperties => { app.features_pct = pct_at(mouse.row, la.y, la.height); } ResizeHandle::PropertiesGeometry => { if let Some(pa) = props_area { app.properties_pct = pct_at(mouse.row, pa.y, pa.height); } } ResizeHandle::FileBrowserLeftRight => { app.file_left_pct = pct_at(mouse.column, area.x, area.width); } ResizeHandle::FileBrowserPreviewFilter => { if let Some(rc) = right_col { app.file_preview_pct = file_browser_preview_pct_clamped( pct_at(mouse.row, rc.y, rc.height), app.file_filter_pct, ); } } ResizeHandle::FileBrowserFilterInfo => { if let Some(rc) = right_col { app.file_filter_pct = file_browser_filter_pct_clamped( pct_at(mouse.row, rc.y, rc.height) .saturating_sub(app.file_preview_pct), app.file_preview_pct, ); } } } app.invalidate(); continue; } if app.mode == ViewMode::MbtilesMap && let MouseEventKind::Drag(MouseButton::Left) = mouse.kind && let (Some(area), Some(ref mut mbt)) = (map_area, app.mbt_state.as_mut()) && let Some((lc, lr)) = mbt.map_drag_last { let dc = i32::from(mouse.column) - i32::from(lc); let dr = i32::from(mouse.row) - i32::from(lr); if dc != 0 || dr != 0 { mbt.pan_by_pixels(area.width, area.height, dc, dr); mbt.map_drag_last = Some((mouse.column, mouse.row)); app.needs_redraw = true; } } let prev = app.hovered.clone(); app.hovered = None; if app.mode == ViewMode::LayerOverview { let hover_ok = !matches!( app.tree_items.get(app.selected_index), Some(TreeItem::Feature { layer, feat }) if !app.expanded_features.contains(&(*layer, *feat)) ); if hover_ok && let Some(area) = tree_area && let Some(row) = click_row_in_area( mouse.column, mouse.row, area, app.tree_scroll as usize, ) && let Some((l, f, p)) = app.tree_items.get(row).and_then(TreeItem::layer_feat_part) { app.hovered = Some(HoveredInfo::new(row, l, f, p)); } if app.hovered.is_none() && let Some(area) = map_area && point_in_rect(mouse.column, mouse.row, area) { let b = app.get_bounds(); let rx = f64::from(mouse.column - area.x) / f64::from(area.width); let ry = f64::from(mouse.row - area.y) / f64::from(area.height); let cx = b.0 + rx * (b.2 - b.0); let cy = b.3 - ry * (b.3 - b.1); app.find_hovered_feature(cx, cy, b); } } if app.hovered != prev { let allow = last_hover_redraw .is_none_or(|t| t.elapsed() >= HOVER_REDRAW_THROTTLE); if allow { last_hover_redraw = Some(Instant::now()); app.invalidate(); } } // MbtilesMap: update hover on mouse move over the map. if app.mode == ViewMode::MbtilesMap && let Some(area) = map_area && point_in_rect(mouse.column, mouse.row, area) && let Some(ref mut mbt) = app.mbt_state { let rx = f64::from(mouse.column - area.x) / f64::from(area.width); let ry = f64::from(mouse.row - area.y) / f64::from(area.height); let (wx, wy) = mbt.viewport_world_at_fracs(rx, ry); let prev_hov = mbt.hovered.clone(); mbt.find_hovered(wx, wy); if mbt.hovered != prev_hov { let allow = last_hover_redraw .is_none_or(|t| t.elapsed() >= HOVER_REDRAW_THROTTLE); if allow { last_hover_redraw = Some(Instant::now()); app.invalidate(); } } } } MouseEventKind::ScrollUp | MouseEventKind::ScrollDown => { let up = matches!(mouse.kind, MouseEventKind::ScrollUp); let s = app.scroll_step(); let step = u16::try_from(s)?; // MbtilesMap: scroll zooms in/out centred on the cursor. if app.mode == ViewMode::MbtilesMap && let (Some(area), Some(ref mut mbt)) = (map_area, app.mbt_state.as_mut()) { if point_in_rect(mouse.column, mouse.row, area) { let rx = f64::from(mouse.column - area.x) / f64::from(area.width); let ry = f64::from(mouse.row - area.y) / f64::from(area.height); let (wx, wy) = mbt.viewport_world_at_fracs(rx, ry); mbt.zoom_wheel_at(wx, wy, up); app.needs_redraw = true; continue; } // Properties panel scroll if left_area.is_some_and(|a| point_in_rect(mouse.column, mouse.row, a)) { app.properties_scroll = scroll_by(app.properties_scroll, step, up); app.invalidate(); continue; } } if app.mode == ViewMode::FileBrowser { if filter_area .is_some_and(|a| point_in_rect(mouse.column, mouse.row, a)) { app.filter_scroll = scroll_by(app.filter_scroll, step, up); app.invalidate(); continue; } if info_area.is_some_and(|a| point_in_rect(mouse.column, mouse.row, a)) { app.file_info_scroll = scroll_by(app.file_info_scroll, step, up); app.invalidate(); continue; } } if app.mode == ViewMode::LayerOverview { if props_area.is_some_and(|a| point_in_rect(mouse.column, mouse.row, a)) { app.properties_scroll = scroll_by(app.properties_scroll, step, up); app.invalidate(); continue; } if let Some(area) = tree_area && point_in_rect(mouse.column, mouse.row, area) { if up { app.tree_scroll = app.tree_scroll.saturating_sub(step); } else { let inner = area.height.saturating_sub(2) as usize; let max = u16::try_from(app.tree_items.len().saturating_sub(inner))?; app.tree_scroll = app.tree_scroll.saturating_add(step).min(max); } app.invalidate(); continue; } if map_area.is_some_and(|a| point_in_rect(mouse.column, mouse.row, a)) { continue; } } if up { app.move_up_by(s); } else { app.move_down_by(s); } } MouseEventKind::Down(btn) => { if app.mode == ViewMode::MbtilesMap && btn == MouseButton::Left && let (Some(area), Some(ref mut mbt)) = (map_area, app.mbt_state.as_mut()) && point_in_rect(mouse.column, mouse.row, area) { mbt.map_drag_last = Some((mouse.column, mouse.row)); continue; } if app.mode == ViewMode::FileBrowser { if let Some(fl) = file_left { let dx = fl.x + fl.width; if mouse.column >= dx.saturating_sub(DIVIDER_GRAB) && mouse.column < dx.saturating_add(DIVIDER_GRAB) && mouse.row >= fl.y && mouse.row < fl.y + fl.height { app.resizing = Some(ResizeHandle::FileBrowserLeftRight); app.invalidate(); continue; } } if let (Some(pa), Some(fa)) = (preview_area, filter_area) { let mut invalidate = |value: u16, resizer: ResizeHandle| { if mouse.row >= value.saturating_sub(DIVIDER_GRAB) && mouse.row < value.saturating_add(DIVIDER_GRAB) && right_col.is_some_and(|rc| { point_in_rect(mouse.column, mouse.row, rc) }) { app.resizing = Some(resizer); app.invalidate(); true } else { false } }; let dy_preview = pa.y + pa.height; if invalidate(dy_preview, ResizeHandle::FileBrowserPreviewFilter) { continue; } let dy_filter = fa.y + fa.height; if invalidate(dy_filter, ResizeHandle::FileBrowserFilterInfo) { continue; } } if let Some(fa) = filter_area && point_in_rect(mouse.column, mouse.row, fa) { let row = mouse.row.saturating_sub(fa.y + 1) as usize + app.filter_scroll as usize; handle_filter_click(app, row); continue; } if let Some(ia) = info_area && point_in_rect(mouse.column, mouse.row, ia) && app.filtered_file_indices.is_empty() && !app.files.is_empty() { let row = mouse.row.saturating_sub(ia.y + 1) as usize; if row == 2 { app.ext_filters.clear(); app.geom_filters.clear(); app.algo_filters.clear(); app.rebuild_filtered_files(); } continue; } if let Some(area) = app.file_table_area { if app.data_loaded() && let Some(widths) = app.file_table_widths && let Some(c) = file_header_click_column( area, &widths, mouse.column, mouse.row, ) { app.handle_file_header_click(c); continue; } if let Some(row) = click_row_in_area( mouse.column, mouse.row, area, app.file_list_state.offset(), ) && row < app.filtered_file_indices.len() { let dbl = last_file_click.is_some_and(|(t, prev)| { prev == row && t.elapsed().as_millis() < 400 }); last_file_click = Some((Instant::now(), row)); app.selected_file_index = row; app.file_list_state.select(Some(row)); app.invalidate_bounds(); if dbl { app.handle_enter(); } } } } else if app.mode == ViewMode::LayerOverview { if let (Some(la), Some(ta)) = (left_area, tree_area) && let Some(h) = divider_hit(mouse.column, mouse.row, la, ta, geom_area) { app.resizing = Some(h); app.invalidate(); continue; } if let Some(area) = tree_area && let Some(row) = click_row_in_area( mouse.column, mouse.row, area, app.tree_scroll as usize, ) && row < app.tree_items.len() { let dbl = last_tree_click.is_some_and(|(t, prev)| { prev == row && t.elapsed().as_millis() < 400 }); last_tree_click = Some((Instant::now(), row)); if let Some((l, f, p)) = app.tree_items.get(row).and_then(TreeItem::layer_feat_part) { app.handle_feature_click(l, f, p, area.height); } else { app.selected_index = row; app.scroll_selected_into_view( area.height.saturating_sub(2) as usize ); } app.invalidate_bounds(); if dbl { app.handle_enter(); } } if let Some(ref h) = app.hovered && let Some(ta) = tree_area && map_area .is_some_and(|m| point_in_rect(mouse.column, mouse.row, m)) { app.handle_feature_click(h.layer, h.feat, h.part, ta.height); app.invalidate_bounds(); } } } _ => {} }, Event::Resize(_, _) => app.invalidate(), _ => {} } } } Ok(()) } /// Apply scroll delta: subtract if up, add if down. fn scroll_by(val: u16, step: u16, up: bool) -> u16 { if up { val.saturating_sub(step) } else { val.saturating_add(step) } } // --- Filtering --- fn handle_filter_click(app: &mut App, row: usize) { let exts = collect_extensions(&app.files); let geoms = collect_file_geometries(&app.files); let algos = collect_file_algorithms(&app.files); let ext_start = 3; let ext_end = ext_start + exts.len(); let geom_start = ext_end + 2; let geom_end = geom_start + geoms.len(); let algo_start = geom_end + 2; let algo_end = algo_start + algos.len(); if row == 0 { app.ext_filters.clear(); app.geom_filters.clear(); app.algo_filters.clear(); } else if row >= ext_start && row < ext_end { toggle_set_string(&mut app.ext_filters, &exts[row - ext_start]); } else if row >= geom_start && row < geom_end { toggle_set(&mut app.geom_filters, geoms[row - geom_start]); } else if row >= algo_start && row < algo_end { toggle_set(&mut app.algo_filters, algos[row - algo_start]); } app.rebuild_filtered_files(); } fn toggle_set(set: &mut HashSet, val: T) { if !set.remove(&val) { set.insert(val); } } fn toggle_set_string(set: &mut HashSet, val: &str) { if !set.remove(val) { set.insert(val.to_string()); } } pub(crate) fn collect_file_geometries(files: &[LsRow]) -> Vec { let mut set = HashSet::new(); for row in files { if let LsRow::Info { info, .. } = row { for g in &info.geometries { set.insert(*g); } } } let mut v: Vec<_> = set.into_iter().collect(); v.sort_unstable(); v } pub(crate) fn collect_file_algorithms(files: &[LsRow]) -> Vec { let mut set = HashSet::new(); for row in files { if let LsRow::Info { info, .. } = row { for a in &info.algorithms { set.insert(*a); } } } let mut v: Vec<_> = set.into_iter().collect(); v.sort_by_key(FileAlgorithm::to_string); v } fn collect_extensions(files: &[LsRow]) -> Vec { let mut set = HashSet::new(); for row in files { if let Some(ext) = row.path().extension().and_then(|e| e.to_str()) { set.insert(ext.to_lowercase()); } } let mut v: Vec<_> = set.into_iter().collect(); v.sort_unstable(); v } // --- Geometry helpers --- fn geometry_type_name(geom: &Geometry) -> &'static str { GeometryType::try_from(geom).map_or("Unknown", Into::into) } fn geometry_color(geom: &Geometry) -> Color { match geom { Geometry::::MultiPoint(_) => CLR_MULTI_POINT, Geometry::::LineString(_) => CLR_LINE, Geometry::::MultiLineString(_) => CLR_MULTI_LINE, Geometry::::Polygon(_) | Geometry::::MultiPolygon(_) if has_bad_winding(geom) => { CLR_BAD_WINDING } Geometry::::Polygon(_) => CLR_POLYGON, Geometry::::MultiPolygon(_) => CLR_MULTI_POLYGON, Geometry::::Point(_) | Geometry::::Line(_) | Geometry::::GeometryCollection(_) | Geometry::::Rect(_) | Geometry::::Triangle(_) => CLR_POINT, } } fn multi_part_count(geom: &Geometry) -> usize { match geom { Geometry::::MultiPoint(mp) => mp.0.len(), Geometry::::MultiLineString(mls) => mls.0.len(), Geometry::::MultiPolygon(mpoly) => mpoly.0.len(), _ => 0, } } fn poly_ring_stats(poly: &Polygon) -> (usize, usize) { let ring_count = 1 + poly.interiors().len(); let total_verts = poly.exterior().0.len() + poly.interiors().iter().map(|r| r.0.len()).sum::(); (total_verts, ring_count) } fn feature_suffix(geom: &Geometry) -> String { let n = multi_part_count(geom); if n > 0 { return format!(" ({n} parts)"); } match geom { Geometry::::LineString(ls) => format!(" ({}v)", ls.0.len()), Geometry::::Polygon(poly) => { let (total, ring_count) = poly_ring_stats(poly); if ring_count > 1 { format!(" ({total}v, {ring_count} rings)") } else { format!(" ({total}v)") } } _ => String::new(), } } fn sub_feature_suffix(geom: &Geometry, part: usize) -> String { match geom { Geometry::::MultiLineString(mls) => mls .0 .get(part) .map_or(String::new(), |ls| format!(" ({}v)", ls.0.len())), Geometry::::MultiPolygon(mpoly) => mpoly.0.get(part).map_or(String::new(), |poly| { let (total, ring_count) = poly_ring_stats(poly); if ring_count > 1 { format!(" ({total}v, {ring_count} rings)") } else { format!(" ({total}v)") } }), _ => String::new(), } } fn is_entry_visible(layer: usize, feat: usize, sel: &TreeItem) -> bool { match sel { TreeItem::All => true, TreeItem::Layer(l) => *l == layer, TreeItem::Feature { layer: l, feat: f } | TreeItem::SubFeature { layer: l, feat: f, .. } => *l == layer && *f == feat, } } fn part_color(sel: Option, hov: Option, idx: usize, base: Color) -> Color { if sel == Some(idx) { CLR_SELECTED } else if hov == Some(idx) { CLR_HOVERED } else if sel.is_some() || hov.is_some() { CLR_DIMMED } else { base } } // --- Winding --- fn ring_signed_area(ring: &[Coord]) -> f64 { let mut area = 0.0; for w in ring.windows(2) { let [x1, y1] = coord_f64(w[0]); let [x2, y2] = coord_f64(w[1]); area += (x2 - x1) * (y2 + y1); } if let (Some(&last), Some(&first)) = (ring.last(), ring.first()) { let [lx, ly] = coord_f64(last); let [fx, fy] = coord_f64(first); area += (fx - lx) * (fy + ly); } area } pub(crate) fn is_ring_ccw(ring: &[Coord]) -> bool { ring_signed_area(ring) < 0.0 } fn has_bad_winding(geom: &Geometry) -> bool { let check = |poly: &Polygon| { !is_ring_ccw(&poly.exterior().0) || poly.interiors().iter().any(|r| is_ring_ccw(&r.0)) }; match geom { Geometry::::Polygon(poly) => check(poly), Geometry::::MultiPolygon(mpoly) => mpoly.iter().any(check), _ => false, } } // --- Spatial index --- struct GeometryIndexEntry { layer: usize, feat: usize, part: Option, vertices: Vec<[f64; 2]>, } impl RTreeObject for GeometryIndexEntry { type Envelope = AABB<[f64; 2]>; fn envelope(&self) -> Self::Envelope { if self.vertices.is_empty() { return AABB::from_point([0.0, 0.0]); } let (min_x, min_y, max_x, max_y) = self.vertices.iter().fold( ( f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY, ), |(ax, ay, bx, by), v| (ax.min(v[0]), ay.min(v[1]), bx.max(v[0]), by.max(v[1])), ); AABB::from_corners([min_x, min_y], [max_x, max_y]) } } impl PointDistance for GeometryIndexEntry { fn distance_2(&self, point: &[f64; 2]) -> f64 { self.vertices .iter() .map(|v| { let dx = v[0] - point[0]; let dy = v[1] - point[1]; dx * dx + dy * dy }) .fold(f64::INFINITY, f64::min) } } #[must_use] pub fn coord_f64(c: Coord) -> [f64; 2] { [f64::from(c.x), f64::from(c.y)] } ================================================ FILE: rust/mlt/src/ui/rendering/files.rs ================================================ use std::collections::HashSet; use ratatui::Frame; use ratatui::layout::{Alignment, Constraint, Rect}; use ratatui::prelude::{Line, Span, Style}; use ratatui::widgets::{Cell, HighlightSpacing, Paragraph, Row, Table, Wrap}; use size_format::SizeFormatterSI; use crate::ls::{LsRow, NA, na, path_display, row_cells_6}; use crate::ui::rendering::map; use crate::ui::state::App; use crate::ui::{ CLR_DIMMED, CLR_HINT, CLR_HOVERED, STYLE_BOLD, STYLE_LABEL, STYLE_SELECTED, block_with_title, collect_extensions, collect_file_algorithms, collect_file_geometries, }; pub fn render_tile_preview_panel(f: &mut Frame<'_>, area: Rect, app: &App) { if let Some(ref fc) = app.preview_fc { map::render_tile_preview(f, area, fc, app.preview_extent); } else { let msg = if app .get_selected_file() .and_then(|r| { app.preview_load_requested .as_ref() .filter(|p| p.as_path() == r.path()) }) .is_some() { "Loading…" } else { "Select a tile file (.mlt / .mvt) to preview" }; f.render_widget( Paragraph::new(Line::from(msg)).block(block_with_title("Tile Preview")), area, ); } } pub fn render_file_browser(f: &mut Frame<'_>, area: Rect, app: &mut App) { app.file_table_area = Some(area); app.file_table_inner_height = area.height.saturating_sub(3) as usize; let base = app.file_browser_base.as_deref(); let file_w = app .files .iter() .map(|r| path_display(r.path(), base).len()) .max() .unwrap_or(4) .max(4); let widths = [ Constraint::Length(u16::try_from(file_w).unwrap_or_default().min(200)), Constraint::Length(8), Constraint::Length(7), Constraint::Length(6), Constraint::Length(10), Constraint::Min(0), ]; app.file_table_widths = Some(widths); let header = Row::new(vec![ Cell::from("File"), Cell::from(Line::from("Size").alignment(Alignment::Right)), Cell::from(Line::from("Enc %").alignment(Alignment::Right)), Cell::from(Line::from("Layers").alignment(Alignment::Right)), Cell::from(Line::from("Features").alignment(Alignment::Right)), Cell::from("Notes"), ]) .style(STYLE_BOLD); let rows: Vec = app .filtered_file_indices .iter() .map(|&i| Row::new(row_cells_6(&app.files[i], base).map(Cell::from))) .collect(); let sort_hint = if app.data_loaded() { " Click header to sort" } else { "" }; let filtered = app.filtered_file_indices.len(); let total = app.files.len(); let count = if filtered < total { format!("{filtered}/{total}") } else { total.to_string() }; let title = format!("MLT Files ({count} found) - ↑/↓ navigate, Enter open, h help, q quit{sort_hint}"); let table = Table::new(rows, widths) .header(header) .column_spacing(1) .block(block_with_title(title)) .row_highlight_style(STYLE_SELECTED) .highlight_symbol(">> ") .highlight_spacing(HighlightSpacing::Always); f.render_stateful_widget(table, area, &mut app.file_list_state); } pub fn render_file_filter_panel(f: &mut Frame<'_>, area: Rect, app: &mut App) { let exts = collect_extensions(&app.files); let geoms = collect_file_geometries(&app.files); let algos = collect_file_algorithms(&app.files); let has_any = !app.ext_filters.is_empty() || !app.geom_filters.is_empty() || !app.algo_filters.is_empty(); let selected_mlt = app.get_selected_file(); let sel_ext: Option = selected_mlt .and_then(|r| r.path().extension().and_then(|e| e.to_str())) .map(str::to_lowercase); let sel_info = selected_mlt.and_then(|r| match r { LsRow::Info { info, .. } => Some(info), _ => None, }); let mut lines: Vec> = Vec::new(); let reset_style = if has_any { STYLE_SELECTED } else { Style::default().fg(CLR_DIMMED) }; lines.push(Line::from(Span::styled("[Reset filters]", reset_style))); lines.push(Line::from("")); let check = |on: bool| if on { "[x] " } else { "[ ] " }; let present_style = |yes: bool| -> Style { Style::default().fg(if yes { CLR_HOVERED } else { CLR_DIMMED }) }; if !exts.is_empty() { lines.push(Line::from(Span::styled("Extensions:", STYLE_BOLD))); for ext in &exts { lines.push(Line::from(Span::styled( format!(" {}{ext}", check(app.ext_filters.contains(ext))), present_style(sel_ext.as_deref() == Some(ext.as_str())), ))); } } if !geoms.is_empty() { lines.push(Line::from("")); lines.push(Line::from(Span::styled("Geometry Types:", STYLE_BOLD))); let sel_geoms: HashSet<_> = sel_info .map(|i| i.geometries.iter().copied().collect()) .unwrap_or_default(); for g in &geoms { lines.push(Line::from(Span::styled( format!(" {}{g}", check(app.geom_filters.contains(g))), present_style(sel_geoms.contains(g)), ))); } } if !algos.is_empty() { lines.push(Line::from("")); lines.push(Line::from(Span::styled("Algorithms:", STYLE_BOLD))); let sel_algos: HashSet<_> = sel_info .map(|i| i.algorithms.iter().copied().collect()) .unwrap_or_default(); for a in &algos { lines.push(Line::from(Span::styled( format!(" {}{a}", check(app.algo_filters.contains(a))), present_style(sel_algos.contains(a)), ))); } } if lines.is_empty() { lines.push(Line::from("(loading…)")); } let inner = area.height.saturating_sub(2); let max = u16::try_from(lines.len().saturating_sub(inner as usize)).unwrap_or(0); app.filter_scroll = app.filter_scroll.min(max); let para = Paragraph::new(lines) .block(block_with_title("Filter (click to toggle)")) .scroll((app.filter_scroll, 0)); f.render_widget(para, area); } pub fn render_file_info_panel(f: &mut Frame<'_>, area: Rect, app: &mut App) { let info = app.get_selected_file().and_then(|r| match r { LsRow::Info { info, .. } => Some(info), _ => None, }); let lines: Vec> = if let Some(info) = info { let sz = |n: usize| format!("{:.1}B", SizeFormatterSI::new(n as u64)); let row = |name: &str, val: String, desc: &str| -> Line<'static> { let mut spans = vec![ Span::styled(format!("{name}: "), STYLE_LABEL), Span::raw(val), ]; if !desc.is_empty() { spans.push(Span::styled( format!(" {desc}"), Style::default().fg(CLR_HINT), )); } Line::from(spans) }; vec![ row("File", info.path.clone(), ""), row("Size", sz(info.size), "raw MLT file size"), row( "Encoding", na(info.encoding_pct.map(|p| format!("{p:.1}%"))), "MLT / (data + metadata)", ), row("Data", na(info.data_size.map(&sz)), "decoded payload size"), row( "Metadata", match (info.meta_size, info.meta_pct) { (Some(m), Some(p)) => format!("{} ({:.1}% of data)", sz(m), p), _ => NA.to_string(), }, "encoding overhead", ), row("Layers", info.layers.to_string(), "tile layer count"), row( "Features", info.features.to_string(), "total across all layers", ), row( "Streams", na(info.streams.map(|n| n.to_string())), "encoded data streams", ), row( "Geometries", info.geometries_display(), "geometry types present", ), row( "Algorithms", info.algorithms_display(), "compression methods", ), ] } else if app.filtered_file_indices.is_empty() && !app.files.is_empty() { vec![ Line::from("No files match the current filters."), Line::from(""), Line::from(Span::styled("[Reset filters]", STYLE_SELECTED)), ] } else { vec![Line::from("Select a file to view details")] }; let inner = area.height.saturating_sub(2) as usize; let max = u16::try_from(lines.len().saturating_sub(inner)).unwrap_or(0); app.file_info_scroll = app.file_info_scroll.min(max); let para = Paragraph::new(lines) .block(block_with_title("File Info")) .wrap(Wrap { trim: false }) .scroll((app.file_info_scroll, 0)); f.render_widget(para, area); } ================================================ FILE: rust/mlt/src/ui/rendering/help.rs ================================================ use ratatui::Frame; use ratatui::layout::{Alignment, Rect}; use ratatui::style::{Color, Style}; use ratatui::text::{Line, Span}; use ratatui::widgets::{Block, Borders, Padding, Paragraph, Wrap}; use crate::ui::state::{App, ViewMode}; use crate::ui::{ CLR_BAD_WINDING, CLR_DIMMED, CLR_EXTENT, CLR_HOVERED, CLR_INNER_RING, CLR_INNER_RING_SEL, CLR_LINE, CLR_MULTI_LINE, CLR_MULTI_POINT, CLR_MULTI_POLYGON, CLR_POINT, CLR_POLYGON, CLR_SELECTED, STYLE_LABEL, STYLE_SELECTED, block_with_title, }; const CLR_ERROR: Color = Color::Red; pub fn render_error_popup(f: &mut Frame<'_>, app: &App) { let Some((ref filename, ref msg)) = app.error_popup else { return; }; let area = f.area(); let lines: Vec> = msg .trim() .lines() .map(|s| Line::from(s.to_string())) .collect(); let line_count = lines.len(); let height = u16::try_from(line_count) .unwrap_or(u16::MAX) .saturating_add(5) .min(28) .min(area.height.saturating_sub(4)); let width = 80.min(area.width.saturating_sub(8)); let popup = Rect::new( area.x + (area.width.saturating_sub(width)) / 2, area.y + (area.height.saturating_sub(height)) / 2, width, height, ); f.render_widget(ratatui::widgets::Clear, popup); let error_block = Block::default() .borders(Borders::ALL) .border_style(Style::default().fg(CLR_ERROR)) .title_style( Style::default() .fg(CLR_ERROR) .add_modifier(ratatui::style::Modifier::BOLD), ) .title_top(format!(" Unable to open {filename} ")) .title_bottom(Line::from("any key to close").right_aligned()) .padding(Padding::uniform(1)); let para = Paragraph::new(lines) .block(error_block) .alignment(Alignment::Center) .wrap(Wrap { trim: true }); f.render_widget(para, popup); } pub fn render_help_overlay(f: &mut Frame<'_>, app: &mut App) { let area = f.area(); let lines = match app.mode { ViewMode::FileBrowser => help_file_browser(), ViewMode::LayerOverview => help_layer_overview(), ViewMode::MbtilesMap => help_mbtiles_map(), }; let height = u16::try_from(lines.len()) .unwrap_or(u16::MAX) .saturating_add(2) .min(area.height.saturating_sub(2)); let width = 62.min(area.width.saturating_sub(4)); let popup = Rect::new( area.x + (area.width.saturating_sub(width)) / 2, area.y + (area.height.saturating_sub(height)) / 2, width, height, ); let inner = height.saturating_sub(2); let max = u16::try_from(lines.len().saturating_sub(inner as usize)).unwrap_or(0); app.help_scroll = app.help_scroll.min(max); f.render_widget(ratatui::widgets::Clear, popup); let para = Paragraph::new(lines) .block(block_with_title( "Help (↑/↓/scroll to navigate, any other key to close)", )) .scroll((app.help_scroll, 0)); f.render_widget(para, popup); } fn heading(text: &str) -> Line<'static> { Line::from(Span::styled(text.to_string(), STYLE_SELECTED)) } fn key(k: &str, desc: &str) -> Line<'static> { Line::from(vec![ Span::styled(format!(" {k:<20}"), STYLE_LABEL), Span::raw(desc.to_string()), ]) } fn color(c: Color, label: &str, desc: &str) -> Line<'static> { Line::from(vec![ Span::raw(" "), Span::styled(format!("{label:<20}"), Style::default().fg(c)), Span::raw(desc.to_string()), ]) } fn help_file_browser() -> Vec> { vec![ heading("Keyboard"), key("? h F1", "Toggle this help"), key("q Ctrl+c", "Quit"), key("Up/Down j/k", "Navigate file list"), key("PageUp/PageDown", "Scroll by page"), key("Home/End", "Jump to first/last"), key("Enter", "Open selected MLT file"), key("Esc", "Quit"), Line::from(""), heading("Mouse"), key("Click row", "Select file"), key("Double-click row", "Open file"), key("Click header", "Sort by column"), key("Scroll", "Navigate file list"), key("Drag divider", "Resize panels"), Line::from(""), heading("Filter Panel"), key("Click checkbox", "Toggle geometry/algorithm filter"), key("Click [Reset]", "Clear all filters"), ] } fn help_mbtiles_map() -> Vec> { vec![ heading("Keyboard"), key("? h F1", "Toggle this help"), key("q Ctrl+c Esc", "Quit"), Line::from(""), heading("Mouse"), key("Scroll on map", "Zoom ±0.5 levels (centred on cursor)"), key("Left-drag on map", "Pan"), key("Hover over map", "Inspect feature properties in left panel"), Line::from(""), heading("Map Colors"), color(CLR_POINT, "Magenta", "Point"), color(CLR_LINE, "Cyan", "LineString"), color(CLR_POLYGON, "Blue", "Polygon"), color(CLR_EXTENT, "Dark gray", "Tile boundaries"), color(CLR_HOVERED, "White", "Hovered feature"), ] } fn help_layer_overview() -> Vec> { vec![ heading("Keyboard"), key("? h F1", "Toggle this help"), key("q Ctrl+c", "Quit"), key("Esc", "Back to file browser"), key("Up/Down j/k", "Navigate feature tree"), key("PageUp/PageDown", "Scroll by page"), key("Home/End", "Jump to first/last"), key("Enter", "Expand/collapse layer or feature"), key("+ = Right", "Expand selected node"), key("-", "Collapse (or jump to parent)"), key("*", "Expand/collapse all layers"), key("Left", "Jump to parent node"), key("Ctrl+h / Ctrl+l", "Resize left/right split"), key("Shift+J / Shift+K", "Resize top/bottom split"), Line::from(""), heading("Mouse"), key("Click tree item", "Select (drill into level)"), key("Double-click", "Expand/collapse"), key("Hover tree/map", "Highlight geometry"), key("Click on map", "Select hovered feature"), key("Scroll panels", "Scroll tree/properties"), key("Drag dividers", "Resize panels"), Line::from(""), heading("Map Colors"), color(CLR_POINT, "Magenta", "Point"), color(CLR_MULTI_POINT, "Light magenta", "MultiPoint"), color(CLR_LINE, "Cyan", "LineString"), color(CLR_MULTI_LINE, "Light cyan", "MultiLineString"), color(CLR_POLYGON, "Blue", "Polygon (outer ring, CCW)"), color(CLR_MULTI_POLYGON, "Light blue", "MultiPolygon"), color(CLR_INNER_RING, "Red", "Inner ring (hole, CW)"), color(CLR_BAD_WINDING, "Light red", "Non-standard winding"), color(CLR_EXTENT, "Dark gray", "Tile extent boundary"), Line::from(""), heading("Selection Colors"), color(CLR_SELECTED, "Yellow", "Selected feature/part"), color(CLR_HOVERED, "White", "Hovered feature"), color(CLR_INNER_RING_SEL, "Salmon", "Inner ring (selected)"), color(CLR_DIMMED, "Dark gray", "Sibling parts (dimmed)"), ] } ================================================ FILE: rust/mlt/src/ui/rendering/layers.rs ================================================ use mlt_core::geo_types::{ Geometry, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, }; use mlt_core::geojson::Feature; use ratatui::Frame; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::prelude::{Line, Modifier, Span, Style}; use ratatui::widgets::{Paragraph, Wrap}; use crate::ui::mbt::MbtTileData; use crate::ui::state::{App, TreeItem, ViewMode}; use crate::ui::{ CLR_HOVERED_TREE, STYLE_LABEL, STYLE_SELECTED, block_with_title, feature_suffix, geometry_color, geometry_type_name, is_ring_ccw, stat_line, sub_feature_suffix, }; pub fn render_tree_panel(f: &mut Frame<'_>, area: Rect, app: &mut App) { let lines: Vec> = app .tree_items .iter() .enumerate() .map(|(idx, item)| { let (text, base) = match item { TreeItem::All => ("All".into(), None), TreeItem::Layer(li) => { let g = &app.layer_groups[*li]; let n = g.feature_indices.len(); let first = g .feature_indices .first() .map(|&gi| geometry_type_name(&app.fc.features[gi].geometry)); let all_same = first.is_some_and(|ft| { g.feature_indices .iter() .all(|&gi| geometry_type_name(&app.fc.features[gi].geometry) == ft) }); let label = if all_same && n > 0 { format!("{}s", first.unwrap()) } else { "features".into() }; ( format!(" Layer: {} ({n} {label}, extent {})", g.name, g.extent), None, ) } TreeItem::Feature { layer, feat } => { let geom = &app.feature(*layer, *feat).geometry; ( format!( " Feat {feat}: {}{}", geometry_type_name(geom), feature_suffix(geom) ), Some(geometry_color(geom)), ) } TreeItem::SubFeature { layer, feat, part } => { let geom = &app.feature(*layer, *feat).geometry; let n = match geom { Geometry::::MultiPoint(_) => "Point", Geometry::::MultiLineString(_) => "LineString", Geometry::::MultiPolygon(_) => "Polygon", _ => "Part", }; ( format!(" Part {part}: {n}{}", sub_feature_suffix(geom, *part)), Some(geometry_color(geom)), ) } }; let style = if idx == app.selected_index { STYLE_SELECTED } else if app.hovered.as_ref().is_some_and(|h| h.tree_idx == idx) { Style::default() .fg(CLR_HOVERED_TREE) .add_modifier(Modifier::UNDERLINED) } else { base.map_or(Style::default(), |c| Style::default().fg(c)) }; Line::from(vec![ Span::raw(if idx == app.selected_index { ">> " } else { " " }), Span::styled(text, style), ]) }) .collect(); let title = match app.mode { ViewMode::LayerOverview => { let name = app .current_file .as_ref() .and_then(|p| p.file_name()) .and_then(|n| n.to_str()) .unwrap_or("unknown"); format!("{name} - Enter/+/-:expand, Esc:back, h:help, q:quit") } ViewMode::FileBrowser | ViewMode::MbtilesMap => "Features".into(), }; let inner = area.height.saturating_sub(2) as usize; app.tree_inner_height = inner; let max = u16::try_from(app.tree_items.len().saturating_sub(inner)).unwrap_or(0); app.tree_scroll = app.tree_scroll.min(max); let para = Paragraph::new(lines) .block(block_with_title(title)) .scroll((app.tree_scroll, 0)); f.render_widget(para, area); } fn feature_property_lines(feat: &Feature) -> Vec> { let lines: Vec> = feat .properties .iter() .filter(|(k, _)| *k != "_layer" && *k != "_extent") .map(|(k, v)| { Line::from(vec![ Span::styled(format!("{k}: "), STYLE_LABEL), Span::raw(match v { serde_json::Value::String(s) => s.clone(), _ => v.to_string(), }), ]) }) .collect(); if lines.is_empty() { vec![Line::from("(no properties)")] } else { lines } } pub fn render_properties_panel(f: &mut Frame<'_>, area: Rect, app: &mut App) -> Rect { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([ Constraint::Percentage(app.properties_pct), Constraint::Percentage(100u16.saturating_sub(app.properties_pct)), ]) .split(area); render_properties_top(f, chunks[0], app); render_geometry_stats(f, chunks[1], app); chunks[1] } fn render_properties_top(f: &mut Frame<'_>, area: Rect, app: &mut App) { let item = app.tree_items.get(app.selected_index); let hov = app.hovered.as_ref(); let (title, lines): (String, Vec>) = match item { None | Some(TreeItem::All | TreeItem::Layer(_)) => { if let Some(h) = hov { let key = (h.layer, h.feat); if app.last_properties_key != Some(key) { app.properties_scroll = 0; app.last_properties_key = Some(key); } ( format!("Properties (feat {}, hover)", h.feat), feature_property_lines(app.feature(h.layer, h.feat)), ) } else { app.last_properties_key = None; ( "Properties".into(), vec![Line::from( "Select or hover over a feature to view properties", )], ) } } Some(TreeItem::Feature { layer, feat } | TreeItem::SubFeature { layer, feat, .. }) => { let key = (*layer, *feat); if app.last_properties_key != Some(key) { app.properties_scroll = 0; app.last_properties_key = Some(key); } ( format!("Properties (feat {feat})"), feature_property_lines(app.feature(*layer, *feat)), ) } }; let inner = area.height.saturating_sub(2); let max = u16::try_from(lines.len().saturating_sub(inner as usize)).unwrap_or(0); app.properties_scroll = app.properties_scroll.min(max); let para = Paragraph::new(lines) .block(block_with_title(title)) .wrap(Wrap { trim: true }) .scroll((app.properties_scroll, 0)); f.render_widget(para, area); } fn info_point(lines: &mut Vec>, p: Point) { lines.push(stat_line("Coords", &format!("{:?}", <[i32; 2]>::from(p)))); } fn info_line_string(lines: &mut Vec>, ls: &LineString) { lines.push(stat_line("Vertices", &ls.0.len())); } fn info_polygon(lines: &mut Vec>, poly: &Polygon) { let total: usize = poly.exterior().0.len() + poly.interiors().iter().map(|r| r.0.len()).sum::(); lines.push(stat_line("Vertices", &total)); lines.push(stat_line("Rings", &(1 + poly.interiors().len()))); let ext = &poly.exterior().0; let w = if is_ring_ccw(ext) { "CCW" } else { "CW" }; lines.push(Line::from(format!(" Ring 0: {}v, {w}", ext.len()))); for (i, ring) in poly.interiors().iter().enumerate() { let w = if is_ring_ccw(&ring.0) { "CCW" } else { "CW" }; lines.push(Line::from(format!( " Ring {}: {}v, {w}", i + 1, ring.0.len() ))); } } fn info_multi_point(lines: &mut Vec>, pts: &MultiPoint) { lines.push(stat_line("Points", &pts.0.len())); } fn info_multi_line_string(lines: &mut Vec>, mls: &MultiLineString) { let total: usize = mls.iter().map(|ls| ls.0.len()).sum(); lines.push(stat_line("Parts", &mls.0.len())); lines.push(stat_line("Vertices", &total)); } fn info_multi_polygon(lines: &mut Vec>, mpoly: &MultiPolygon) { let total: usize = mpoly .iter() .flat_map(|p| { std::iter::once(p.exterior().0.len()).chain(p.interiors().iter().map(|r| r.0.len())) }) .sum(); let total_rings: usize = mpoly.iter().map(|p| 1 + p.interiors().len()).sum(); lines.push(stat_line("Parts", &mpoly.0.len())); lines.push(stat_line("Total vertices", &total)); lines.push(stat_line("Total rings", &total_rings)); } fn geometry_stats_lines(geom: &Geometry) -> Vec> { let mut lines = vec![stat_line("Type", &geometry_type_name(geom))]; match geom { Geometry::::Point(p) => info_point(&mut lines, *p), Geometry::::LineString(ls) => info_line_string(&mut lines, ls), Geometry::::Polygon(poly) => info_polygon(&mut lines, poly), Geometry::::MultiPoint(pts) => info_multi_point(&mut lines, pts), Geometry::::MultiLineString(mls) => info_multi_line_string(&mut lines, mls), Geometry::::MultiPolygon(mpoly) => info_multi_polygon(&mut lines, mpoly), _ => unreachable!("Unexpected geometry type {geom:?}"), } lines } fn subpart_stats_lines(geom: &Geometry, part: usize) -> Vec> { let mut lines = vec![stat_line( "Component", &format!("part #{} of a {}", part, geometry_type_name(geom)), )]; match geom { Geometry::::MultiPoint(pts) => { if let Some(p) = pts.0.get(part) { lines.push(stat_line("Type", &"Point")); info_point(&mut lines, *p); } } Geometry::::MultiLineString(mls) => { if let Some(ls) = mls.0.get(part) { lines.push(stat_line("Type", &"LineString")); info_line_string(&mut lines, ls); } } Geometry::::MultiPolygon(mpoly) => { if let Some(poly) = mpoly.0.get(part) { lines.push(stat_line("Type", &"Polygon")); info_polygon(&mut lines, poly); } } _ => {} } lines } // --------------------------------------------------------------------------- // MBTiles hover properties panel // --------------------------------------------------------------------------- /// Renders the left panel for `MbtilesMap` mode: shows hovered feature properties only. pub fn render_mbtiles_hover_panel(f: &mut Frame<'_>, area: Rect, app: &mut App) { let (title, lines) = mbt_hover_title_and_lines(app); let inner = area.height.saturating_sub(2) as usize; let max = u16::try_from(lines.len().saturating_sub(inner)).unwrap_or(0); app.properties_scroll = app.properties_scroll.min(max); let para = Paragraph::new(lines) .block(block_with_title(title)) .wrap(Wrap { trim: true }) .scroll((app.properties_scroll, 0)); f.render_widget(para, area); } fn mbt_hover_title_and_lines(app: &App) -> (String, Vec>) { let Some(ref mbt) = app.mbt_state else { return ("Properties".into(), vec![Line::from("No mbtiles loaded")]); }; let Some(ref h) = mbt.hovered else { return ( "Properties".into(), vec![Line::from("Hover over a feature to inspect properties")], ); }; let tile_entry = mbt.tiles.get(&h.tile); let Some(MbtTileData::Loaded { fc, layer_groups, .. }) = tile_entry else { let msg: String = match tile_entry { Some(MbtTileData::Empty) => "Tile empty (no vector data)".into(), Some(MbtTileData::Error(e)) => { let snippet: String = e.chars().take(160).collect(); format!("Tile error: {snippet}") } None | Some(MbtTileData::Loading | MbtTileData::Loaded { .. }) => { "Tile loading…".into() } }; return ("Properties".into(), vec![Line::from(msg)]); }; let Some(group) = layer_groups.get(h.layer_idx) else { return ("Properties".into(), vec![Line::from("(feature not found)")]); }; let Some(&gi) = group.feature_indices.get(h.feat_idx) else { return ("Properties".into(), vec![Line::from("(feature not found)")]); }; let feat = &fc.features[gi]; let (z, tx, ty) = h.tile; let title = format!( "Properties — {} feat {} (tile {z}/{tx}/{ty})", group.name, h.feat_idx ); (title, feature_property_lines(feat)) } fn render_geometry_stats(f: &mut Frame<'_>, area: Rect, app: &App) { let item = app.tree_items.get(app.selected_index); let hov = app.hovered.as_ref(); let lines = match item { Some(TreeItem::SubFeature { layer, feat, part }) => { subpart_stats_lines(&app.feature(*layer, *feat).geometry, *part) } Some(TreeItem::Feature { layer, feat }) => { geometry_stats_lines(&app.feature(*layer, *feat).geometry) } _ => { if let Some(h) = hov { let geom = &app.feature(h.layer, h.feat).geometry; match h.part { Some(p) => subpart_stats_lines(geom, p), None => geometry_stats_lines(geom), } } else { vec![Line::from( "Select or hover over a feature to view geometry info", )] } } }; let para = Paragraph::new(lines) .block(block_with_title("Geometry")) .wrap(Wrap { trim: false }); f.render_widget(para, area); } ================================================ FILE: rust/mlt/src/ui/rendering/map.rs ================================================ use std::collections::HashSet; use mlt_core::geo_types::{Coord, Geometry, Polygon}; use mlt_core::geojson::FeatureCollection; use ratatui::Frame; use ratatui::layout::Rect; use ratatui::prelude::{Span, Style}; use ratatui::style::Color; use ratatui::widgets::canvas::{Canvas, Context, Line as CanvasLine, Rectangle}; use crate::ui::mbt::{MbtHoveredInfo, MbtTileData, TileTransform}; use crate::ui::state::{App, LayerGroup, TreeItem}; use crate::ui::{ CLR_DIMMED, CLR_EXTENT, CLR_HOVERED, CLR_INNER_RING, CLR_INNER_RING_SEL, CLR_POLYGON, CLR_SELECTED, block_with_title, coord_f64, geometry_color, is_ring_ccw, part_color, }; pub fn render_map_panel(f: &mut Frame<'_>, area: Rect, app: &App) { let sel = app.selected_item(); let ext = app.extent(); let (x0, y0, x1, y1) = app.calculate_bounds(); let canvas = Canvas::default() .block(block_with_title("Map View")) .x_bounds([x0, x1]) .y_bounds([y0, y1]) .paint(|ctx| { ctx.draw(&Rectangle { x: 0.0, y: 0.0, width: f64::from(ext), height: f64::from(ext), color: CLR_EXTENT, }); let hov = app.hovered.as_ref(); let draw_feat = |ctx: &mut Context<'_>, gi: usize| { let geom = &app.fc.features[gi].geometry; let base = geometry_color(geom); let is_hov = hov.is_some_and(|h| app.global_idx(h.layer, h.feat) == gi); let sel_part = match sel { TreeItem::SubFeature { layer, feat, part } if app.global_idx(*layer, *feat) == gi => { Some(*part) } _ => None, }; let hov_part = hov.and_then(|h| (app.global_idx(h.layer, h.feat) == gi).then_some(h.part)?); draw_feature(ctx, geom, base, is_hov, sel_part, hov_part); }; match sel { TreeItem::All => { for gi in 0..app.fc.features.len() { draw_feat(ctx, gi); } } TreeItem::Layer(l) => { for &gi in &app.layer_groups[*l].feature_indices { draw_feat(ctx, gi); } } TreeItem::Feature { layer, feat } | TreeItem::SubFeature { layer, feat, .. } => { draw_feat(ctx, app.global_idx(*layer, *feat)); } } }); f.render_widget(canvas, area); } /// Full-tile preview for file browser (all layers, no r-tree/mouse). pub fn render_tile_preview(f: &mut Frame<'_>, area: Rect, fc: &FeatureCollection, extent: u32) { let canvas = Canvas::default() .block(block_with_title("Tile Preview")) .x_bounds([0.0, f64::from(extent)]) .y_bounds([0.0, f64::from(extent)]) .paint(|ctx| { ctx.draw(&Rectangle { x: 0.0, y: 0.0, width: f64::from(extent), height: f64::from(extent), color: CLR_EXTENT, }); for feat in &fc.features { draw_feature( ctx, &feat.geometry, geometry_color(&feat.geometry), false, None, None, ); } }); f.render_widget(canvas, area); } fn draw_feature( ctx: &mut Context<'_>, geom: &Geometry, base: Color, is_hov: bool, sel_part: Option, hov_part: Option, ) { let color = if is_hov { CLR_HOVERED } else { base }; match geom { Geometry::::Point(p) => draw_point(ctx, p.0, color), Geometry::::LineString(ls) => draw_line(ctx, &ls.0, color), Geometry::::Polygon(poly) => draw_polygon(ctx, poly, is_hov, color), Geometry::::MultiPoint(pts) => { for (i, p) in pts.iter().enumerate() { draw_point(ctx, p.0, part_color(sel_part, hov_part, i, color)); } } Geometry::::MultiLineString(lines) => { for (i, ls) in lines.iter().enumerate() { draw_line(ctx, &ls.0, part_color(sel_part, hov_part, i, color)); } } Geometry::::MultiPolygon(polys) => { for (i, poly) in polys.iter().enumerate() { let pc = part_color(sel_part, hov_part, i, color); draw_polygon(ctx, poly, matches!(pc, CLR_HOVERED | CLR_SELECTED), pc); } } _ => {} } } fn draw_point(ctx: &mut Context<'_>, c: Coord, color: Color) { let [x, y] = coord_f64(c); ctx.print(x, y, Span::styled("×", Style::default().fg(color))); } fn draw_line(ctx: &mut Context<'_>, coords: &[Coord], color: Color) { for w in coords.windows(2) { let [x1, y1] = coord_f64(w[0]); let [x2, y2] = coord_f64(w[1]); ctx.draw(&CanvasLine::new(x1, y1, x2, y2, color)); } } fn draw_ring(ctx: &mut Context<'_>, ring: &[Coord], color: Color) { draw_line(ctx, ring, color); if let (Some(&last), Some(&first)) = (ring.last(), ring.first()) { let [lx, ly] = coord_f64(last); let [fx, fy] = coord_f64(first); ctx.draw(&CanvasLine::new(lx, ly, fx, fy, color)); } } fn ring_color(ring: &[Coord], highlighted: bool, fallback: Color) -> Color { if !highlighted { if is_ring_ccw(ring) { CLR_POLYGON } else { CLR_INNER_RING } } else if is_ring_ccw(ring) { fallback } else { CLR_INNER_RING_SEL } } fn draw_polygon(ctx: &mut Context<'_>, poly: &Polygon, highlighted: bool, fallback: Color) { let ext = &poly.exterior().0; draw_ring(ctx, ext, ring_color(ext, highlighted, fallback)); for ring in poly.interiors() { draw_ring(ctx, &ring.0, ring_color(&ring.0, highlighted, fallback)); } } // --------------------------------------------------------------------------- // MBTiles world-map rendering // --------------------------------------------------------------------------- /// Draw all layers from a decoded tile (`data_tile` is the MVT source key used for hover match). #[allow(clippy::too_many_arguments)] // Canvas draw context + tile payload + viewport Y range. fn draw_mbtiles_loaded_tile_layers( ctx: &mut Context<'_>, hovered: Option<&MbtHoveredInfo>, data_tile: (u8, u32, u32), fc: &FeatureCollection, extent: u32, layer_groups: &[LayerGroup], vy0: f64, vy1: f64, ) { let (tz, tx, ty) = data_tile; let transform = TileTransform::new(tz, tx, ty, extent); let hov_gi = hovered.and_then(|h| { if h.tile != (tz, tx, ty) { return None; } layer_groups .get(h.layer_idx) .and_then(|g| g.feature_indices.get(h.feat_idx)) .copied() }); for group in layer_groups { for &gi in &group.feature_indices { let feat = &fc.features[gi]; let base = geometry_color(&feat.geometry); let is_hov = hov_gi == Some(gi); let color = if is_hov { CLR_HOVERED } else { base }; draw_geom_world(ctx, &feat.geometry, &transform, vy0, vy1, color); } } } /// Render the interactive world map for an .mbtiles file. /// /// World coordinate space: `x ∈ [0, 1]` west→east, `y ∈ [0, 1]` north→south. /// /// `y_bounds` must be `[min_y, max_y]` with `min_y < max_y` for Ratatui's `Painter` clip math. /// The painter maps **larger** world Y toward the **top** of the widget, so we reflect each /// geographic `wy` with [`mbt_screen_y`] to get north-up on screen. pub fn render_mbtiles_map_panel(f: &mut Frame<'_>, area: Rect, app: &App) { let Some(ref mbt) = app.mbt_state else { return; }; let visible = mbt.visible_tiles(); let (cz, cx, cy) = mbt.center_tile_xyz(); let title = format!( "World Map — {cz}/{cx}/{cy} — zoom {:.1} drag=pan hover=info q/Esc quit", mbt.zoom_f ); let canvas = Canvas::default() .block(block_with_title(title)) .x_bounds([mbt.vp_x0, mbt.vp_x1]) .y_bounds([mbt.vp_y0, mbt.vp_y1]) .paint(|ctx| { let vy0 = mbt.vp_y0; let vy1 = mbt.vp_y1; // Under native resolution: draw each loaded ancestor at most once (world-aligned). let mut overzoom_drawn: HashSet<(u8, u32, u32)> = HashSet::new(); for &(tz, tx, ty) in &visible { if matches!( mbt.tiles.get(&(tz, tx, ty)), Some(MbtTileData::Loaded { .. }) ) { continue; } let Some((sz, sx, sy)) = mbt.find_overzoom_source(tz, tx, ty) else { continue; }; if !overzoom_drawn.insert((sz, sx, sy)) { continue; } let Some(MbtTileData::Loaded { fc, extent, layer_groups, .. }) = mbt.tiles.get(&(sz, sx, sy)) else { continue; }; draw_mbtiles_loaded_tile_layers( ctx, mbt.hovered.as_ref(), (sz, sx, sy), fc, *extent, layer_groups, vy0, vy1, ); } for &(tz, tx, ty) in &visible { let n = f64::from(1u32 << tz); let x0 = f64::from(tx) / n; let y0 = f64::from(ty) / n; let x1 = f64::from(tx + 1) / n; let y1 = f64::from(ty + 1) / n; // Draw tile border. draw_world_rect_vp(ctx, vy0, vy1, x0, y0, x1, y1, CLR_EXTENT); let Some(tile_data) = mbt.tiles.get(&(tz, tx, ty)) else { continue; }; match tile_data { MbtTileData::Loading => { let cx = f64::midpoint(x0, x1); let cy = f64::midpoint(y0, y1); let sy = mbt_screen_y(vy0, vy1, cy); ctx.print(cx, sy, Span::styled("…", Style::default().fg(CLR_DIMMED))); } MbtTileData::Loaded { fc, extent, layer_groups, .. } => { draw_mbtiles_loaded_tile_layers( ctx, mbt.hovered.as_ref(), (tz, tx, ty), fc, *extent, layer_groups, vy0, vy1, ); } MbtTileData::Empty | MbtTileData::Error(_) => {} } } }); f.render_widget(canvas, area); } /// Map geographic world Y to canvas Y so north is at the top of the map widget. #[inline] fn mbt_screen_y(vp_y0: f64, vp_y1: f64, wy: f64) -> f64 { vp_y0 + vp_y1 - wy } /// Draw the four edges of a world-coordinate tile rectangle (north-up). #[allow(clippy::too_many_arguments)] // Ratatui canvas line API; args map 1:1 to world corners + style. fn draw_world_rect_vp( ctx: &mut Context<'_>, vp_y0: f64, vp_y1: f64, x0: f64, y0: f64, x1: f64, y1: f64, color: Color, ) { let s0 = mbt_screen_y(vp_y0, vp_y1, y0); let s1 = mbt_screen_y(vp_y0, vp_y1, y1); ctx.draw(&CanvasLine::new(x0, s0, x1, s0, color)); ctx.draw(&CanvasLine::new(x0, s1, x1, s1, color)); ctx.draw(&CanvasLine::new(x0, s0, x0, s1, color)); ctx.draw(&CanvasLine::new(x1, s0, x1, s1, color)); } /// Draw a geometry in world coordinates using the provided tile transform (north-up on canvas). fn draw_geom_world( ctx: &mut Context<'_>, geom: &Geometry, t: &TileTransform, vp_y0: f64, vp_y1: f64, color: Color, ) { match geom { Geometry::::Point(p) => { let [wx, wy] = t.to_world(p.0); let sy = mbt_screen_y(vp_y0, vp_y1, wy); ctx.print(wx, sy, Span::styled("×", Style::default().fg(color))); } Geometry::::LineString(ls) => draw_world_line(ctx, &ls.0, t, vp_y0, vp_y1, color), Geometry::::Polygon(poly) => { draw_world_ring(ctx, &poly.exterior().0, t, vp_y0, vp_y1, color); for ring in poly.interiors() { draw_world_ring(ctx, &ring.0, t, vp_y0, vp_y1, color); } } Geometry::::MultiPoint(mp) => { for p in mp.iter() { let [wx, wy] = t.to_world(p.0); let sy = mbt_screen_y(vp_y0, vp_y1, wy); ctx.print(wx, sy, Span::styled("×", Style::default().fg(color))); } } Geometry::::MultiLineString(mls) => { for ls in mls.iter() { draw_world_line(ctx, &ls.0, t, vp_y0, vp_y1, color); } } Geometry::::MultiPolygon(mpoly) => { for poly in mpoly.iter() { draw_world_ring(ctx, &poly.exterior().0, t, vp_y0, vp_y1, color); for ring in poly.interiors() { draw_world_ring(ctx, &ring.0, t, vp_y0, vp_y1, color); } } } _ => {} } } fn draw_world_line( ctx: &mut Context<'_>, coords: &[Coord], t: &TileTransform, vp_y0: f64, vp_y1: f64, color: Color, ) { for w in coords.windows(2) { let [xa, ya] = t.to_world(w[0]); let [xb, yb] = t.to_world(w[1]); let sa = mbt_screen_y(vp_y0, vp_y1, ya); let sb = mbt_screen_y(vp_y0, vp_y1, yb); ctx.draw(&CanvasLine::new(xa, sa, xb, sb, color)); } } fn draw_world_ring( ctx: &mut Context<'_>, ring: &[Coord], t: &TileTransform, vp_y0: f64, vp_y1: f64, color: Color, ) { draw_world_line(ctx, ring, t, vp_y0, vp_y1, color); if let (Some(&last), Some(&first)) = (ring.last(), ring.first()) { let [lx, ly] = t.to_world(last); let [fx, fy] = t.to_world(first); ctx.draw(&CanvasLine::new( lx, mbt_screen_y(vp_y0, vp_y1, ly), fx, mbt_screen_y(vp_y0, vp_y1, fy), color, )); } } ================================================ FILE: rust/mlt/src/ui/rendering/mod.rs ================================================ pub mod files; pub mod help; pub mod layers; pub mod map; ================================================ FILE: rust/mlt/src/ui/state.rs ================================================ use std::collections::HashSet; use std::path::{Path, PathBuf}; use std::sync::mpsc; use std::time::Instant; use mlt_core::GeometryType; use mlt_core::geo_types::{Geometry, Polygon}; use mlt_core::geojson::{Feature, FeatureCollection}; use ratatui::layout::{Constraint, Rect}; use ratatui::widgets::TableState; use rstar::{PointDistance as _, RTree}; use crate::ls::{FileAlgorithm, FileSortColumn, LsRow}; use crate::ui::mbt::MbtilesState; use crate::ui::{ GeometryIndexEntry, auto_expand, coord_f64, group_by_layer, is_entry_visible, load_fc, multi_part_count, }; #[derive(Debug, Clone, PartialEq, Eq)] pub enum ViewMode { FileBrowser, LayerOverview, /// Interactive world map viewer for .mbtiles files. MbtilesMap, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ResizeHandle { LeftRight, FeaturesProperties, PropertiesGeometry, FileBrowserLeftRight, FileBrowserPreviewFilter, FileBrowserFilterInfo, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum TreeItem { All, Layer(usize), Feature { layer: usize, feat: usize, }, SubFeature { layer: usize, feat: usize, part: usize, }, } impl TreeItem { pub(crate) fn layer_feat_part(&self) -> Option<(usize, usize, Option)> { match self { Self::Feature { layer, feat } => Some((*layer, *feat, None)), Self::SubFeature { layer, feat, part } => Some((*layer, *feat, Some(*part))), _ => None, } } } #[derive(Clone, PartialEq, Eq)] pub struct HoveredInfo { pub(crate) tree_idx: usize, pub(crate) layer: usize, pub(crate) feat: usize, pub(crate) part: Option, } impl HoveredInfo { pub fn new(tree_idx: usize, layer: usize, feat: usize, part: Option) -> Self { Self { tree_idx, layer, feat, part, } } } pub struct LayerGroup { pub(crate) name: String, pub(crate) extent: u32, pub(crate) feature_indices: Vec, } impl LayerGroup { pub fn new(name: String, extent: u32, feature_indices: Vec) -> Self { Self { name, extent, feature_indices, } } } pub type PreviewValue = (PathBuf, Result<(FeatureCollection, u32), ()>); pub struct App { pub(crate) mode: ViewMode, pub(crate) files: Vec, pub(crate) selected_file_index: usize, pub(crate) file_list_state: TableState, pub(crate) analysis_rx: Option>>, /// Base path for file browser; used when drawing to show relative paths. pub(crate) file_browser_base: Option, file_sort: Option<(FileSortColumn, bool)>, pub(crate) file_table_area: Option, pub(crate) file_table_widths: Option<[Constraint; 6]>, pub(crate) current_file: Option, pub(crate) fc: FeatureCollection, pub(crate) layer_groups: Vec, pub(crate) tree_items: Vec, pub(crate) selected_index: usize, pub(crate) hovered: Option, expanded_layers: Vec, pub(crate) expanded_features: HashSet<(usize, usize)>, last_scroll_time: Instant, scroll_speed: usize, pub(crate) needs_redraw: bool, cached_bounds: Option<(f64, f64, f64, f64)>, cached_bounds_key: usize, pub(crate) left_pct: u16, pub(crate) features_pct: u16, pub(crate) resizing: Option, pub(crate) properties_scroll: u16, pub(crate) tree_scroll: u16, pub(crate) tree_inner_height: usize, pub(crate) last_properties_key: Option<(usize, usize)>, pub(crate) properties_pct: u16, geometry_index: Option>, pub(crate) file_left_pct: u16, pub(crate) file_preview_pct: u16, pub(crate) file_filter_pct: u16, pub(crate) ext_filters: HashSet, pub(crate) geom_filters: HashSet, pub(crate) algo_filters: HashSet, pub(crate) filter_scroll: u16, pub(crate) filtered_file_indices: Vec, pub(crate) file_table_inner_height: usize, pub(crate) show_help: bool, pub(crate) help_scroll: u16, pub(crate) error_popup: Option<(String, String)>, pub(crate) file_info_scroll: u16, pub(crate) preview_tile_path: Option, pub(crate) preview_fc: Option, pub(crate) preview_extent: u32, pub(crate) preview_rx: Option>, pub(crate) preview_load_requested: Option, /// State for the `MbtilesMap` mode (Some only when mode == `MbtilesMap`). pub(crate) mbt_state: Option>, } impl Default for App { fn default() -> Self { Self { mode: ViewMode::FileBrowser, files: Vec::new(), selected_file_index: 0, file_list_state: TableState::default(), analysis_rx: None, file_browser_base: None, file_sort: None, file_table_area: None, file_table_widths: None, current_file: None, fc: FeatureCollection { features: Vec::new(), ty: "FeatureCollection".into(), }, layer_groups: Vec::new(), tree_items: Vec::new(), selected_index: 0, hovered: None, expanded_layers: Vec::new(), expanded_features: HashSet::new(), last_scroll_time: Instant::now(), scroll_speed: 1, needs_redraw: true, cached_bounds: None, cached_bounds_key: usize::MAX, left_pct: 30, features_pct: 50, resizing: None, properties_scroll: 0, last_properties_key: None, tree_scroll: 0, tree_inner_height: 0, properties_pct: 50, geometry_index: None, file_left_pct: 70, file_preview_pct: 33, file_filter_pct: 33, ext_filters: HashSet::new(), geom_filters: HashSet::new(), algo_filters: HashSet::new(), filter_scroll: 0, filtered_file_indices: Vec::new(), file_table_inner_height: 10, show_help: false, help_scroll: 0, error_popup: None, file_info_scroll: 0, preview_tile_path: None, preview_fc: None, preview_extent: 4096, preview_rx: None, preview_load_requested: None, mbt_state: None, } } } impl App { pub(crate) fn new_file_browser( files: Vec, analysis_rx: Option>>, base_path: PathBuf, ) -> Self { let mut file_list_state = TableState::default(); file_list_state.select(Some(0)); let filtered_file_indices = (0..files.len()).collect(); Self { files, file_list_state, analysis_rx, file_browser_base: Some(base_path), filtered_file_indices, ..Self::default() } } pub(crate) fn new_mbtiles(mbt_state: MbtilesState, path: PathBuf) -> Self { Self { mode: ViewMode::MbtilesMap, current_file: Some(path), mbt_state: Some(Box::new(mbt_state)), ..Self::default() } } pub(crate) fn new_single_file(fc: FeatureCollection, path: Option) -> Self { let layer_groups = group_by_layer(&fc); let expanded_layers = auto_expand(&layer_groups); let mut app = Self { mode: ViewMode::LayerOverview, current_file: path, expanded_layers, layer_groups, fc, ..Self::default() }; app.build_geometry_index(); app.build_tree_items(); app } pub(crate) fn data_loaded(&self) -> bool { self.analysis_rx.is_none() && !self .files .iter() .any(|r| matches!(r, LsRow::Loading { .. })) } pub(crate) fn open_help(&mut self) { self.show_help = true; self.help_scroll = 0; self.invalidate(); } pub(crate) fn handle_file_header_click(&mut self, col: FileSortColumn) { if !self.data_loaded() { return; } let prev = self.get_selected_file().map(|r| r.path().to_path_buf()); let asc = !matches!(self.file_sort, Some((c, a)) if c == col && a); self.file_sort = Some((col, asc)); self.files.sort_by(|a, b| file_cmp(a, b, col, asc)); self.rebuild_filtered_files(); if let Some(path) = prev && let Some(pos) = self .filtered_file_indices .iter() .position(|&i| self.files[i].path() == path.as_path()) { self.selected_file_index = pos; self.file_list_state.select(Some(pos)); } } fn load_file(&mut self, path: &Path) -> anyhow::Result<()> { self.fc = load_fc(path)?; self.layer_groups = group_by_layer(&self.fc); self.current_file = Some(path.to_path_buf()); self.mode = ViewMode::LayerOverview; self.expanded_layers = auto_expand(&self.layer_groups); self.expanded_features.clear(); self.build_geometry_index(); self.build_tree_items(); self.selected_index = 0; self.invalidate_bounds(); Ok(()) } pub(crate) fn global_idx(&self, layer: usize, feat: usize) -> usize { self.layer_groups[layer].feature_indices[feat] } pub(crate) fn feature(&self, layer: usize, feat: usize) -> &Feature { &self.fc.features[self.global_idx(layer, feat)] } pub(crate) fn extent(&self) -> u32 { self.layer_groups.first().map_or(4096, |g| g.extent) } pub(crate) fn selected_item(&self) -> &TreeItem { &self.tree_items[self.selected_index] } fn build_tree_items(&mut self) { self.tree_items.clear(); self.tree_items.push(TreeItem::All); for (li, group) in self.layer_groups.iter().enumerate() { self.tree_items.push(TreeItem::Layer(li)); if !self.expanded_layers.get(li).copied().unwrap_or(false) { continue; } for (fi, &gi) in group.feature_indices.iter().enumerate() { self.tree_items.push(TreeItem::Feature { layer: li, feat: fi, }); if self.expanded_features.contains(&(li, fi)) { for part in 0..multi_part_count(&self.fc.features[gi].geometry) { self.tree_items.push(TreeItem::SubFeature { layer: li, feat: fi, part, }); } } } } } fn build_geometry_index(&mut self) { let mut entries: Vec = Vec::new(); for (li, group) in self.layer_groups.iter().enumerate() { for (fi, &gi) in group.feature_indices.iter().enumerate() { let geom = &self.fc.features[gi].geometry; let n = multi_part_count(geom); let parts: Vec> = if n == 0 { vec![None] } else { (0..n).map(Some).collect() }; for part in parts { let vertices = geometry_vertices(geom, part); if !vertices.is_empty() { entries.push(GeometryIndexEntry { layer: li, feat: fi, part, vertices, }); } } } } self.geometry_index = (!entries.is_empty()).then(|| RTree::bulk_load(entries)); } pub(crate) fn scroll_step(&mut self) -> usize { let now = Instant::now(); let elapsed = now.duration_since(self.last_scroll_time).as_millis(); self.last_scroll_time = now; self.scroll_speed = match elapsed { 0..50 => (self.scroll_speed + 1).min(20), 50..120 => self.scroll_speed.max(2), _ => 1, }; self.scroll_speed } pub(crate) fn move_up_by(&mut self, n: usize) { self.move_by(n, false); } pub(crate) fn move_down_by(&mut self, n: usize) { self.move_by(n, true); } fn move_by(&mut self, n: usize, down: bool) { match self.mode { ViewMode::FileBrowser => { let prev = self.selected_file_index; let max = self.filtered_file_indices.len().saturating_sub(1); self.selected_file_index = if down { self.selected_file_index.saturating_add(n).min(max) } else { self.selected_file_index.saturating_sub(n).min(max) }; self.file_list_state.select(Some(self.selected_file_index)); if self.selected_file_index != prev { self.invalidate_bounds(); } } ViewMode::LayerOverview => { let prev = self.selected_index; let max = self.tree_items.len().saturating_sub(1); self.selected_index = if down { self.selected_index.saturating_add(n).min(max) } else { self.selected_index.saturating_sub(n) }; if self.selected_index != prev { self.scroll_selected_into_view(self.tree_inner_height); self.invalidate_bounds(); } } ViewMode::MbtilesMap => {} } } pub(crate) fn move_to_start(&mut self) { self.move_up_by(usize::MAX); } pub(crate) fn move_to_end(&mut self) { self.move_down_by(usize::MAX); } pub(crate) fn page_size(&self) -> usize { match self.mode { ViewMode::FileBrowser => self.file_table_inner_height, ViewMode::LayerOverview | ViewMode::MbtilesMap => self.tree_inner_height, } } pub(crate) fn handle_enter(&mut self) { match self.mode { ViewMode::FileBrowser => { let path = self.get_selected_file().map(|r| r.path().to_path_buf()); let (path_str, is_error, error_msg) = self .get_selected_file() .map(|r| { let s = r.path().display().to_string(); match r { LsRow::Error { error, .. } => (s, true, error.clone()), _ => (s, false, String::new()), } }) .unwrap_or_default(); if let Some(path) = path { if is_error { self.error_popup = Some((path_str, error_msg)); self.invalidate(); } else if let Err(e) = self.load_file(&path) { self.error_popup = Some((path_str, e.to_string())); self.invalidate(); } } } ViewMode::LayerOverview => match self.tree_items.get(self.selected_index) { Some(TreeItem::Layer(li)) => { let li = *li; if li < self.expanded_layers.len() { self.expanded_layers[li] = !self.expanded_layers[li]; self.build_tree_items(); self.invalidate(); } } Some(TreeItem::Feature { layer, feat }) => { let key = (*layer, *feat); if multi_part_count(&self.feature(key.0, key.1).geometry) > 0 { if !self.expanded_features.remove(&key) { self.expanded_features.insert(key); } self.build_tree_items(); self.invalidate(); } } _ => {} }, ViewMode::MbtilesMap => {} } } pub(crate) fn handle_plus(&mut self) { if self.mode != ViewMode::LayerOverview { return; } match self.tree_items.get(self.selected_index) { Some(TreeItem::Layer(li)) => { let li = *li; if li < self.expanded_layers.len() && !self.expanded_layers[li] { self.expanded_layers[li] = true; self.build_tree_items(); self.invalidate(); } } Some(TreeItem::Feature { layer, feat }) => { let key = (*layer, *feat); if multi_part_count(&self.feature(key.0, key.1).geometry) > 0 && !self.expanded_features.contains(&key) { self.expanded_features.insert(key); self.build_tree_items(); self.invalidate(); } } _ => {} } } pub(crate) fn handle_minus(&mut self) { if self.mode != ViewMode::LayerOverview { return; } match self.tree_items.get(self.selected_index).cloned() { Some(TreeItem::Layer(li)) if li < self.expanded_layers.len() && self.expanded_layers[li] => { self.expanded_layers[li] = false; self.rebuild_and_clamp(); } Some(TreeItem::Feature { layer, feat }) => { if self.expanded_features.remove(&(layer, feat)) { self.rebuild_and_clamp(); } else if layer < self.expanded_layers.len() && self.expanded_layers[layer] { self.expanded_layers[layer] = false; self.rebuild_and_select(|it| matches!(it, TreeItem::Layer(l) if *l == layer)); } } Some(TreeItem::SubFeature { layer, feat, .. }) => { self.expanded_features.remove(&(layer, feat)); self.rebuild_and_select(|it| { matches!(it, TreeItem::Feature { layer: l, feat: f } if *l == layer && *f == feat) }); } _ => {} } } pub(crate) fn handle_star(&mut self) { if self.mode != ViewMode::LayerOverview { return; } let new = !self.expanded_layers.iter().all(|&e| e); self.expanded_layers.fill(new); self.rebuild_and_clamp(); } pub(crate) fn handle_escape(&mut self) -> bool { match self.mode { ViewMode::FileBrowser | ViewMode::MbtilesMap => true, ViewMode::LayerOverview if self.files.is_empty() => true, ViewMode::LayerOverview => { self.mode = ViewMode::FileBrowser; self.invalidate_bounds(); false } } } pub(crate) fn handle_left_arrow(&mut self) { if self.mode != ViewMode::LayerOverview { return; } let Some(item) = self.tree_items.get(self.selected_index).cloned() else { return; }; let target = match item { TreeItem::SubFeature { layer, feat, .. } => self.tree_items.iter().position(|t| { matches!(t, TreeItem::Feature { layer: l, feat: f } if *l == layer && *f == feat) }), TreeItem::Feature { layer, .. } => self .tree_items .iter() .position(|t| matches!(t, TreeItem::Layer(l) if *l == layer)), TreeItem::Layer(_) => Some(0), TreeItem::All => { if !self.files.is_empty() { self.mode = ViewMode::FileBrowser; } return; } }; if let Some(idx) = target && idx != self.selected_index { self.selected_index = idx; self.invalidate_bounds(); } } fn rebuild_and_clamp(&mut self) { self.build_tree_items(); self.selected_index = self .selected_index .min(self.tree_items.len().saturating_sub(1)); self.invalidate_bounds(); } fn rebuild_and_select(&mut self, pred: impl Fn(&TreeItem) -> bool) { self.build_tree_items(); if let Some(idx) = self.tree_items.iter().position(pred) { self.selected_index = idx; } self.invalidate_bounds(); } pub(crate) fn invalidate(&mut self) { self.needs_redraw = true; } pub(crate) fn invalidate_bounds(&mut self) { self.cached_bounds = None; self.invalidate(); } pub(crate) fn selected_file_real_index(&self) -> Option { self.filtered_file_indices .get(self.selected_file_index) .copied() } pub(crate) fn get_selected_file(&self) -> Option<&LsRow> { self.selected_file_real_index() .and_then(|i| self.files.get(i)) } pub(crate) fn rebuild_filtered_files(&mut self) { let prev = self.selected_file_real_index(); let has_filters = !self.ext_filters.is_empty() || !self.geom_filters.is_empty() || !self.algo_filters.is_empty(); self.filtered_file_indices = (0..self.files.len()) .filter(|&i| { if !has_filters { return true; } let ext = self.files[i] .path() .extension() .and_then(|e| e.to_str()) .map(str::to_lowercase) .unwrap_or_default(); if !self.ext_filters.is_empty() && !self.ext_filters.iter().any(|f| f.as_str() == ext) { return false; } match &self.files[i] { LsRow::Info { info, .. } => { self.geom_filters .iter() .all(|g| info.geometries.contains(g)) && self .algo_filters .iter() .all(|a| info.algorithms.contains(a)) } _ => true, } }) .collect(); let pos = prev .and_then(|ri| self.filtered_file_indices.iter().position(|&i| i == ri)) .unwrap_or(0); self.selected_file_index = pos; self.file_list_state.select(Some(pos)); self.invalidate(); } pub(crate) fn get_bounds(&mut self) -> (f64, f64, f64, f64) { if self.cached_bounds_key != self.selected_index || self.cached_bounds.is_none() { self.cached_bounds = Some(self.calculate_bounds()); self.cached_bounds_key = self.selected_index; } self.cached_bounds.unwrap() } pub fn calculate_bounds(&self) -> (f64, f64, f64, f64) { let sel = self.selected_item(); let ext = self.extent(); let (mut x0, mut y0) = (f64::INFINITY, f64::INFINITY); let (mut x1, mut y1) = (f64::NEG_INFINITY, f64::NEG_INFINITY); let mut update = |v: &[f64; 2]| { x0 = x0.min(v[0]); y0 = y0.min(v[1]); x1 = x1.max(v[0]); y1 = y1.max(v[1]); }; let geoms: Vec<&Geometry> = match sel { TreeItem::All => self.fc.features.iter().map(|f| &f.geometry).collect(), TreeItem::Layer(l) => self.layer_groups[*l] .feature_indices .iter() .map(|&gi| &self.fc.features[gi].geometry) .collect(), TreeItem::Feature { layer, feat } | TreeItem::SubFeature { layer, feat, .. } => { vec![&self.feature(*layer, *feat).geometry] } }; for geom in geoms { for v in geometry_vertices(geom, None) { update(&v); } } x0 = x0.min(0.0); y0 = y0.min(0.0); x1 = x1.max(f64::from(ext)); y1 = y1.max(f64::from(ext)); let px = (x1 - x0) * 0.1; let py = (y1 - y0) * 0.1; (x0 - px, y0 - py, x1 + px, y1 + py) } pub(crate) fn find_hovered_feature(&mut self, cx: f64, cy: f64, bounds: (f64, f64, f64, f64)) { let sel = self.selected_item().clone(); let threshold = (bounds.2 - bounds.0).max(bounds.3 - bounds.1) * 0.02; let thresh_sq = threshold * threshold; let early_exit = thresh_sq * 0.01; let pt = [cx, cy]; let best = if let Some(ref tree) = self.geometry_index { let mut best: Option<(f64, usize, usize, Option)> = None; for e in tree.nearest_neighbor_iter(&pt) { let d = e.distance_2(&pt); if d > thresh_sq { break; } if !is_entry_visible(e.layer, e.feat, &sel) { continue; } if best.is_none_or(|(bd, ..)| d < bd) { best = Some((d, e.layer, e.feat, e.part)); if d < early_exit { break; } } } best } else { None }; self.hovered = best.and_then(|(_, l, f, p)| { self.find_tree_idx_for_feature(l, f, p) .map(|idx| HoveredInfo::new(idx, l, f, p)) }); } fn find_tree_idx_for_feature( &self, layer: usize, feat: usize, part: Option, ) -> Option { for (idx, item) in self.tree_items.iter().enumerate() { match item { TreeItem::Layer(li) if *li == layer && !self.expanded_layers.get(layer).copied().unwrap_or(false) => { return Some(idx); } TreeItem::Feature { layer: l, feat: f } if *l == layer && *f == feat && (part.is_none() || !self.expanded_features.contains(&(layer, feat))) => { return Some(idx); } TreeItem::SubFeature { layer: l, feat: f, part: p, } if *l == layer && *f == feat && part == Some(*p) => { return Some(idx); } _ => {} } } None } fn ensure_layer_expanded(&mut self, layer: usize) { if layer < self.expanded_layers.len() && !self.expanded_layers[layer] { self.expanded_layers[layer] = true; self.build_tree_items(); } } fn select_and_scroll( &mut self, layer: usize, feat: Option, part: Option, tree_height: u16, ) { let inner = tree_height.saturating_sub(2) as usize; self.ensure_layer_expanded(layer); if let Some(f) = feat { if multi_part_count(&self.feature(layer, f).geometry) > 0 { self.expanded_features.insert((layer, f)); self.build_tree_items(); } if let Some(idx) = self.find_tree_idx_for_feature(layer, f, part) { self.selected_index = idx; self.scroll_selected_into_view(inner); } } else if let Some(idx) = self .tree_items .iter() .position(|it| matches!(it, TreeItem::Layer(l) if *l == layer)) { self.selected_index = idx; self.scroll_selected_into_view(inner); } self.invalidate_bounds(); } pub(crate) fn scroll_selected_into_view(&mut self, inner_height: usize) { let idx = self.selected_index; if idx < self.tree_scroll as usize { self.tree_scroll = u16::try_from(idx).unwrap_or(0); } else if inner_height > 0 && idx >= self.tree_scroll as usize + inner_height { self.tree_scroll = u16::try_from(idx.saturating_sub(inner_height.saturating_sub(1))).unwrap_or(0); } } pub(crate) fn handle_feature_click( &mut self, layer: usize, feat: usize, part: Option, tree_height: u16, ) { match self.selected_item().clone() { TreeItem::All => self.select_and_scroll(layer, None, None, tree_height), TreeItem::Layer(_) => { self.select_and_scroll(layer, Some(feat), None, tree_height); } _ => self.select_and_scroll(layer, Some(feat), part, tree_height), } } } fn error_size(row: &LsRow) -> usize { match row { LsRow::Error { size: Some(s), .. } => *s, _ => 0, } } fn file_cmp(a: &LsRow, b: &LsRow, col: FileSortColumn, asc: bool) -> std::cmp::Ordering { use std::cmp::Ordering; let ord = match (a, b) { (LsRow::Info { info: ai, .. }, LsRow::Info { info: bi, .. }) => match col { FileSortColumn::File => ai.path.cmp(&bi.path), FileSortColumn::Size => ai.size.cmp(&bi.size), FileSortColumn::EncPct => match (ai.encoding_pct, bi.encoding_pct) { (Some(a), Some(b)) => a.total_cmp(&b), (None, None) => Ordering::Equal, (None, Some(_)) => Ordering::Greater, (Some(_), None) => Ordering::Less, }, FileSortColumn::Layers => ai.layers.cmp(&bi.layers), FileSortColumn::Features => ai.features.cmp(&bi.features), }, (LsRow::Info { .. }, _) => Ordering::Less, (_, LsRow::Info { .. }) => Ordering::Greater, (LsRow::Error { .. }, LsRow::Error { .. }) => match col { FileSortColumn::Size => error_size(a).cmp(&error_size(b)), _ => a.path().cmp(b.path()), }, _ => a.path().cmp(b.path()), }; if asc { ord } else { ord.reverse() } } fn poly_verts(poly: &Polygon) -> Vec<[f64; 2]> { poly.exterior() .0 .iter() .copied() .chain(poly.interiors().iter().flat_map(|r| r.0.iter().copied())) .map(coord_f64) .collect() } fn geometry_vertices(geom: &Geometry, part: Option) -> Vec<[f64; 2]> { match (geom, part) { (Geometry::::Point(p), None) => vec![coord_f64(p.0)], (Geometry::::LineString(ls), None) => ls.0.iter().copied().map(coord_f64).collect(), (Geometry::::MultiPoint(mp), None) => mp.iter().map(|p| coord_f64(p.0)).collect(), (Geometry::::Polygon(poly), None) => poly_verts(poly), (Geometry::::MultiLineString(mls), None) => mls .iter() .flat_map(|ls| ls.0.iter().copied().map(coord_f64)) .collect(), (Geometry::::MultiPolygon(mpoly), None) => mpoly.iter().flat_map(poly_verts).collect(), (Geometry::::MultiPoint(mp), Some(p)) => { mp.0.get(p) .map(|pt| vec![coord_f64(pt.0)]) .unwrap_or_default() } (Geometry::::MultiLineString(mls), Some(p)) => mls .0 .get(p) .map(|ls| ls.0.iter().copied().map(coord_f64).collect()) .unwrap_or_default(), (Geometry::::MultiPolygon(mpoly), Some(p)) => { mpoly.0.get(p).map(poly_verts).unwrap_or_default() } _ => Vec::new(), } } ================================================ FILE: rust/mlt-core/.gitignore ================================================ /target ================================================ FILE: rust/mlt-core/CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [0.9.0](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-core-v0.8.0...rust-mlt-core-v0.9.0) - 2026-04-29 ### Added - *(rust)* add secondary Hilbert dictionary sorting on select tiles ([#1349](https://github.com/maplibre/maplibre-tile-spec/pull/1349)) ### Fixed - *(rust)* terrible fsst compression ratio due to reusing compressor results for unrelated shared dicts ([#1333](https://github.com/maplibre/maplibre-tile-spec/pull/1333)) ### Other - *(rust)* presence detection ([#1359](https://github.com/maplibre/maplibre-tile-spec/pull/1359)) - *(rust)* simplify encoding ([#1358](https://github.com/maplibre/maplibre-tile-spec/pull/1358)) - *(rust)* refactor low level encoding ([#1355](https://github.com/maplibre/maplibre-tile-spec/pull/1355)) - *(rust)* consolidate encoders with common buffers ([#1352](https://github.com/maplibre/maplibre-tile-spec/pull/1352)) - *(rust)* compute more stats before sort to enable deduplicating presence in v2 ([#1348](https://github.com/maplibre/maplibre-tile-spec/pull/1348)) - *(rust)* simplify StagingId ([#1347](https://github.com/maplibre/maplibre-tile-spec/pull/1347)) - *(rust)* new encoding methods ([#1346](https://github.com/maplibre/maplibre-tile-spec/pull/1346)) - *(rust)* rm "01" from TileLayer01, StagedLayer01 ([#1345](https://github.com/maplibre/maplibre-tile-spec/pull/1345)) - *(rust)* remove unused staging code and eq impl ([#1344](https://github.com/maplibre/maplibre-tile-spec/pull/1344)) - *(rust)* follow up to Presence cleanup ([#1337](https://github.com/maplibre/maplibre-tile-spec/pull/1337)) - *(rust)* simplify debug formatting ([#1336](https://github.com/maplibre/maplibre-tile-spec/pull/1336)) - *(rust)* IdValues→ParsedId+StagedId, simplify presence ([#1334](https://github.com/maplibre/maplibre-tile-spec/pull/1334)) - *(rust)* push fsst training up one level to not gain false statistics ([#1335](https://github.com/maplibre/maplibre-tile-spec/pull/1335)) - *(rust)* rework presence to use bitvec ([#1329](https://github.com/maplibre/maplibre-tile-spec/pull/1329)) ## [0.8.0](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-core-v0.7.0...rust-mlt-core-v0.8.0) - 2026-04-18 ### Fixed - fix(rust) from_tile tessellates when appropriate ([#1314](https://github.com/maplibre/maplibre-tile-spec/pull/1314)) - *(rust)* geo builds on wasm, remove unnecessary feature gates ([#1297](https://github.com/maplibre/maplibre-tile-spec/pull/1297)) ### Other - *(rust)* cleanup Morton structs ([#1310](https://github.com/maplibre/maplibre-tile-spec/pull/1310)) - *(rust)* cleanup dict ranges ([#1313](https://github.com/maplibre/maplibre-tile-spec/pull/1313)) - remove ALP everywhere - it was never implemented ([#1128](https://github.com/maplibre/maplibre-tile-spec/pull/1128)) - *(rust)* use Coord32 instead of tuple ([#1312](https://github.com/maplibre/maplibre-tile-spec/pull/1312)) - *(rust)* rm RawStreamData and EncodedStreamData ([#1309](https://github.com/maplibre/maplibre-tile-spec/pull/1309)) - *(rust)* Coord32 cleanup, dep update ([#1304](https://github.com/maplibre/maplibre-tile-spec/pull/1304)) - *(rust)* update geo, simplify tessellation ([#1305](https://github.com/maplibre/maplibre-tile-spec/pull/1305)) - Add offline docs.rs-style workspace docs check to Rust CI and fix surfaced rustdoc links ([#1295](https://github.com/maplibre/maplibre-tile-spec/pull/1295)) - docs(rust) Fix broken doc links ([#1293](https://github.com/maplibre/maplibre-tile-spec/pull/1293)) ## [0.7.0](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-core-v0.6.0...rust-mlt-core-v0.7.0) - 2026-04-13 ### Added - *(rust)* trigram based shared dictionary selection ([#1283](https://github.com/maplibre/maplibre-tile-spec/pull/1283)) - *(rust)* allow delta-rle picking in stream selection ([#1258](https://github.com/maplibre/maplibre-tile-spec/pull/1258)) - *(rust)* impl memory budget reset ([#1257](https://github.com/maplibre/maplibre-tile-spec/pull/1257)) - *(rust)* enable FPF for id encoding ([#1243](https://github.com/maplibre/maplibre-tile-spec/pull/1243)) - *(rust)* mlt convert, encoder ([#1240](https://github.com/maplibre/maplibre-tile-spec/pull/1240)) - *(rust)* enhance property iteration ([#1225](https://github.com/maplibre/maplibre-tile-spec/pull/1225)) - *(rust)* SharedDict/Plain string enc support ([#1213](https://github.com/maplibre/maplibre-tile-spec/pull/1213)) ### Fixed - don't unwrap in `adjust_alloc` ([#1275](https://github.com/maplibre/maplibre-tile-spec/pull/1275)) - *(rust)* overflow-panic found by the fuzzer ([#1268](https://github.com/maplibre/maplibre-tile-spec/pull/1268)) - *(rust)* fix geometry decoding fuzz bug ([#1264](https://github.com/maplibre/maplibre-tile-spec/pull/1264)) - *(rust)* fix decoding panic found by fuzz ([#1261](https://github.com/maplibre/maplibre-tile-spec/pull/1261)) - *(rust)* speed up encoding by 30% due to dup sort bug ([#1260](https://github.com/maplibre/maplibre-tile-spec/pull/1260)) - *(rust)* disallow mem leaks in rust, fix test ([#1256](https://github.com/maplibre/maplibre-tile-spec/pull/1256)) - *(rust)* test helper accidentally exposed ([#1250](https://github.com/maplibre/maplibre-tile-spec/pull/1250)) ### Other - Use `impl Iterator` in more places ([#1288](https://github.com/maplibre/maplibre-tile-spec/pull/1288)) - *(rust)* remove a few bad performance choices (+6% encoder gain) ([#1287](https://github.com/maplibre/maplibre-tile-spec/pull/1287)) - *(rust)* resolve most of the encoding quality + performance benefits of java ([#1281](https://github.com/maplibre/maplibre-tile-spec/pull/1281)) - *(rust)* Don't allocate for calculating the hilbert params and resulting parameters ([#1285](https://github.com/maplibre/maplibre-tile-spec/pull/1285)) - *(rust)* improve fuzzer ([#1278](https://github.com/maplibre/maplibre-tile-spec/pull/1278)) - *(rust)* make sure we benchmark ourselves against the compressed variants ([#1194](https://github.com/maplibre/maplibre-tile-spec/pull/1194)) - *(rust)* make staging differ optional from non-opt columns ([#1276](https://github.com/maplibre/maplibre-tile-spec/pull/1276)) - *(rust)* cache morton computation ([#1277](https://github.com/maplibre/maplibre-tile-spec/pull/1277)) - benchmark and fix various performance issues in the encoder ([#1273](https://github.com/maplibre/maplibre-tile-spec/pull/1273)) - fix weird unessary allocation ([#1272](https://github.com/maplibre/maplibre-tile-spec/pull/1272)) - *(rust)* reuse buffers, casting ([#1267](https://github.com/maplibre/maplibre-tile-spec/pull/1267)) - *(rust)* Hotpath based profiling ([#1269](https://github.com/maplibre/maplibre-tile-spec/pull/1269)) - *(rust)* add StreamCtx to simplify callbacks ([#1263](https://github.com/maplibre/maplibre-tile-spec/pull/1263)) - *(rust)* remove unused auto_u32/u64 ([#1265](https://github.com/maplibre/maplibre-tile-spec/pull/1265)) - *(rust)* rm obsolete encode_* funcs ([#1262](https://github.com/maplibre/maplibre-tile-spec/pull/1262)) - *(rust)* massive rewrite of the encoder ([#1254](https://github.com/maplibre/maplibre-tile-spec/pull/1254)) - *(rust)* re-enable the fuzzer in CI ([#1244](https://github.com/maplibre/maplibre-tile-spec/pull/1244)) - *(rust)* improve decoding benchmarks ([#1247](https://github.com/maplibre/maplibre-tile-spec/pull/1247)) - *(rust)* clean up utils ([#1251](https://github.com/maplibre/maplibre-tile-spec/pull/1251)) - *(rust)* consolidate utils ([#1248](https://github.com/maplibre/maplibre-tile-spec/pull/1248)) - *(rust)* move frames/v01 -> decoder, adj use ([#1246](https://github.com/maplibre/maplibre-tile-spec/pull/1246)) - *(rust)* move more code to encoder ([#1245](https://github.com/maplibre/maplibre-tile-spec/pull/1245)) - *(rust)* move all encoding code to separate dir ([#1241](https://github.com/maplibre/maplibre-tile-spec/pull/1241)) - *(rust)* streamline StagedStrings and StagedSharedDict ([#1236](https://github.com/maplibre/maplibre-tile-spec/pull/1236)) - *(rust)* add tests with no presence streams ([#1233](https://github.com/maplibre/maplibre-tile-spec/pull/1233)) - *(rust)* encoder rework, rm profiles ([#1232](https://github.com/maplibre/maplibre-tile-spec/pull/1232)) - *(rust)* mv tessellation to core ([#1220](https://github.com/maplibre/maplibre-tile-spec/pull/1220)) - *(rust)* improve synthetics, geojson ([#1210](https://github.com/maplibre/maplibre-tile-spec/pull/1210)) - add large FastPFOR synthetics ([#1205](https://github.com/maplibre/maplibre-tile-spec/pull/1205)) - *(rust)* use -ize spelling ([#1200](https://github.com/maplibre/maplibre-tile-spec/pull/1200)) - *(rust)* implement feature/property iterator and more type state ([#1198](https://github.com/maplibre/maplibre-tile-spec/pull/1198)) - *(rust)* type state to represent fully-decoded layers ([#1171](https://github.com/maplibre/maplibre-tile-spec/pull/1171)) - *(rust)* cleanup Results, minor styling ([#1192](https://github.com/maplibre/maplibre-tile-spec/pull/1192)) ## [0.6.0](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-core-v0.5.0...rust-mlt-core-v0.6.0) - 2026-03-23 ### Other - *(rust)* migrate to Rust fastpfor ([#1190](https://github.com/maplibre/maplibre-tile-spec/pull/1190)) ## [0.5.0](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-core-v0.4.0...rust-mlt-core-v0.5.0) - 2026-03-17 ### Added - *(rust)* allow resorting in the optimiser ([#1133](https://github.com/maplibre/maplibre-tile-spec/pull/1133)) - *(rust)* migrate the property optimiser to the PGO architecture ([#1118](https://github.com/maplibre/maplibre-tile-spec/pull/1118)) - *(rust)* migrate geometrys to allow for PGO ([#1112](https://github.com/maplibre/maplibre-tile-spec/pull/1112)) - *(rust)* PGO based Id optimiser ([#1105](https://github.com/maplibre/maplibre-tile-spec/pull/1105)) ### Other - *(rust)* memory budgeting, codecs ([#1168](https://github.com/maplibre/maplibre-tile-spec/pull/1168)) - *(rust)* minor noop cleanup ([#1167](https://github.com/maplibre/maplibre-tile-spec/pull/1167)) - *(rust)* introduce EncDec decode states ([#1166](https://github.com/maplibre/maplibre-tile-spec/pull/1166)) - *(rust)* rename EncDec variants ([#1165](https://github.com/maplibre/maplibre-tile-spec/pull/1165)) - *(rust)* add stateful decoder ([#1163](https://github.com/maplibre/maplibre-tile-spec/pull/1163)) - *(rust)* rm TryFrom EncodedGeometry,EncodedId ([#1162](https://github.com/maplibre/maplibre-tile-spec/pull/1162)) - *(rust)* rm FromDecoded, use encode(...) ([#1161](https://github.com/maplibre/maplibre-tile-spec/pull/1161)) - *(rust)* finish big refactoring ([#1160](https://github.com/maplibre/maplibre-tile-spec/pull/1160)) - *(rust)* rename to IdValues and GeometryValues ([#1159](https://github.com/maplibre/maplibre-tile-spec/pull/1159)) - *(rust)* mv impls out of models, use full wire round-trips ([#1158](https://github.com/maplibre/maplibre-tile-spec/pull/1158)) - *(chore)* remove into_static, test fixes ([#1154](https://github.com/maplibre/maplibre-tile-spec/pull/1154)) - *(rust)* rework WASM code to use TileLayer ([#1153](https://github.com/maplibre/maplibre-tile-spec/pull/1153)) - *(rust)* remove unnecessary to_owned calls ([#1151](https://github.com/maplibre/maplibre-tile-spec/pull/1151)) - *(rust)* introduce staging types in Rust layer implementation ([#1149](https://github.com/maplibre/maplibre-tile-spec/pull/1149)) - *(rust)* introduce staging types ([#1148](https://github.com/maplibre/maplibre-tile-spec/pull/1148)) - *(rust)* code structure and remove NameRef ([#1147](https://github.com/maplibre/maplibre-tile-spec/pull/1147)) - *(rust)* refactor parsing and encoding code ([#1144](https://github.com/maplibre/maplibre-tile-spec/pull/1144)) - *(rust)* get rid of borrowme, add EncDec enum ([#1141](https://github.com/maplibre/maplibre-tile-spec/pull/1141)) - *(rust)* simplify ID model ([#1139](https://github.com/maplibre/maplibre-tile-spec/pull/1139)) - *(rust)* replace FromEncoded with Decode and decode_into ([#1134](https://github.com/maplibre/maplibre-tile-spec/pull/1134)) - *(rust)* move impls out of model, scalars ([#1132](https://github.com/maplibre/maplibre-tile-spec/pull/1132)) - *(rust)* rm borrowme from Layer01 ([#1129](https://github.com/maplibre/maplibre-tile-spec/pull/1129)) - *(rust)* remove unused defaults ([#1127](https://github.com/maplibre/maplibre-tile-spec/pull/1127)) - *(rust)* use enum_dispatch where appropriate, Cow for name ([#1125](https://github.com/maplibre/maplibre-tile-spec/pull/1125)) - *(rust)* streamline str decoding ([#1121](https://github.com/maplibre/maplibre-tile-spec/pull/1121)) - *(rust)* string performance benchmark ([#1123](https://github.com/maplibre/maplibre-tile-spec/pull/1123)) - refactor!(rust): make `FromEncoded` and `FromDecoded` `pub(crate)` ([#1119](https://github.com/maplibre/maplibre-tile-spec/pull/1119)) - *(rust)* use strum::IntoStaticStr ([#1122](https://github.com/maplibre/maplibre-tile-spec/pull/1122)) - *(rust)* simplify Morton code decoding with SIMD ([#1114](https://github.com/maplibre/maplibre-tile-spec/pull/1114)) - *(rust)* cleanup after big move ([#1113](https://github.com/maplibre/maplibre-tile-spec/pull/1113)) - *(rust)* clean up all module and mod files ([#1111](https://github.com/maplibre/maplibre-tile-spec/pull/1111)) - *(rust)* rename mod layer to frames ([#1110](https://github.com/maplibre/maplibre-tile-spec/pull/1110)) - *(rust)* rm PropValue, consistent Cows ([#1109](https://github.com/maplibre/maplibre-tile-spec/pull/1109)) - *(rust)* move name into DecodedStrings ([#1108](https://github.com/maplibre/maplibre-tile-spec/pull/1108)) - *(rust)* change tests to be based on the new trait based optimization api ([#1104](https://github.com/maplibre/maplibre-tile-spec/pull/1104)) ## [0.4.0](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-core-v0.3.0...rust-mlt-core-v0.4.0) - 2026-03-10 ### Added - plumbing for profile optimization ([#1101](https://github.com/maplibre/maplibre-tile-spec/pull/1101)) - *(rust)* trait based automatic optimiser ([#1093](https://github.com/maplibre/maplibre-tile-spec/pull/1093)) ### Fixed - *(rust)* migrate part of our lengths from usize to u32 ([#1078](https://github.com/maplibre/maplibre-tile-spec/pull/1078)) ### Other - *(rust)* rework our internal data model ([#1099](https://github.com/maplibre/maplibre-tile-spec/pull/1099)) - *(rust)* rename structs to model ([#1100](https://github.com/maplibre/maplibre-tile-spec/pull/1100)) - *(rust)* handle u32->usize, int overflows ([#1097](https://github.com/maplibre/maplibre-tile-spec/pull/1097)) - *(rust)* move all structs to structs.rs ([#1094](https://github.com/maplibre/maplibre-tile-spec/pull/1094)) - renamings in the encoder ([#1090](https://github.com/maplibre/maplibre-tile-spec/pull/1090)) - *(rust)* refactoring step 3 ([#1088](https://github.com/maplibre/maplibre-tile-spec/pull/1088)) - *(rust)* refactor name storage ([#1087](https://github.com/maplibre/maplibre-tile-spec/pull/1087)) - *(rust)* more core renames, rm EncodedValues::typ ([#1084](https://github.com/maplibre/maplibre-tile-spec/pull/1084)) - *(rust)* some more renames of internals ([#1082](https://github.com/maplibre/maplibre-tile-spec/pull/1082)) - *(rust)* noop, only renames for future refactoring ([#1081](https://github.com/maplibre/maplibre-tile-spec/pull/1081)) - make sure we write u32 varints instead of u64 varints ([#1080](https://github.com/maplibre/maplibre-tile-spec/pull/1080)) - use simd for morton decoding ([#1069](https://github.com/maplibre/maplibre-tile-spec/pull/1069)) ## [0.3.0](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-core-v0.2.0...rust-mlt-core-v0.3.0) - 2026-03-08 ### Added - *(rust)* property encoder optimizer ([#1042](https://github.com/maplibre/maplibre-tile-spec/pull/1042)) - *(rust)* Id optimizer impl ([#1043](https://github.com/maplibre/maplibre-tile-spec/pull/1043)) - *(rust)* Geometry optimizer ([#1045](https://github.com/maplibre/maplibre-tile-spec/pull/1045)) ### Other - *(rust)* rm Dict and FsstDict from SharedDict encoding ([#1068](https://github.com/maplibre/maplibre-tile-spec/pull/1068)) - *(rust)* major rework of the shared dict ([#1066](https://github.com/maplibre/maplibre-tile-spec/pull/1066)) - *(rust)* a bit more geotype cleanup ([#1059](https://github.com/maplibre/maplibre-tile-spec/pull/1059)) - *(rust)* cleanup geotype code ([#1058](https://github.com/maplibre/maplibre-tile-spec/pull/1058)) - *(rust)* move geotype code to its own mod ([#1053](https://github.com/maplibre/maplibre-tile-spec/pull/1053)) - *(rust)* Fuzz the return path for roundtrip-ability ([#1044](https://github.com/maplibre/maplibre-tile-spec/pull/1044)) - *(rust)* bump fastpfor, enable SIMD test, cleanup ([#1052](https://github.com/maplibre/maplibre-tile-spec/pull/1052)) - move property tests to be based on the public API ([#1038](https://github.com/maplibre/maplibre-tile-spec/pull/1038)) ## [0.2.0](https://github.com/maplibre/maplibre-tile-spec/compare/mlt-core-v0.1.2...rust-mlt-core-v0.2.0) - 2026-03-04 ### Added - *(rust)* enable configuring the shared dictionarys fully ([#1015](https://github.com/maplibre/maplibre-tile-spec/pull/1015)) - *(rust)* shared dictionary encoding support ([#1006](https://github.com/maplibre/maplibre-tile-spec/pull/1006)) - *(rust)* add rust morton encoding ([#1005](https://github.com/maplibre/maplibre-tile-spec/pull/1005)) - *(rust)* implement `arbitrary::Arbitrary` for simpler fuzzing setups ([#991](https://github.com/maplibre/maplibre-tile-spec/pull/991)) ### Fixed - *(encoders)* F64 encoded as f64 ([#1009](https://github.com/maplibre/maplibre-tile-spec/pull/1009)) - *(rust)* LineString and MultiLineString geometries ([#989](https://github.com/maplibre/maplibre-tile-spec/pull/989)) - *(rust)* encoding of normalized mixed multi + regular geometrys panics ([#981](https://github.com/maplibre/maplibre-tile-spec/pull/981)) ### Other - *(rust)* rework string parsing ([#1026](https://github.com/maplibre/maplibre-tile-spec/pull/1026)) - *(rust)* move optional into EncodedPropValue ([#1024](https://github.com/maplibre/maplibre-tile-spec/pull/1024)) - *(rust)* prop strings mod ([#1023](https://github.com/maplibre/maplibre-tile-spec/pull/1023)) - *(rust)* rename IntEncoder and add IntEncoding ([#1019](https://github.com/maplibre/maplibre-tile-spec/pull/1019)) - *(rust)* move prop internals to "big enum" arch ([#1014](https://github.com/maplibre/maplibre-tile-spec/pull/1014)) - clean up how FromDecoded interacts with strings ([#1011](https://github.com/maplibre/maplibre-tile-spec/pull/1011)) - rename `Encoder` -> `PhysicalCodecs` ([#1010](https://github.com/maplibre/maplibre-tile-spec/pull/1010)) - *(rust)* start fixing rust synthetics ([#1002](https://github.com/maplibre/maplibre-tile-spec/pull/1002)) - *(synthetic)* add Morton fixture synthetic test ([#960](https://github.com/maplibre/maplibre-tile-spec/pull/960)) - More testing around geometry combinations ([#982](https://github.com/maplibre/maplibre-tile-spec/pull/982)) - *(rust)* remove AVX failing test ([#999](https://github.com/maplibre/maplibre-tile-spec/pull/999)) - *(rust)* rm json5, inf floats to string ([#1000](https://github.com/maplibre/maplibre-tile-spec/pull/1000)) - *(rust)* Add an encoding benchmark ([#997](https://github.com/maplibre/maplibre-tile-spec/pull/997)) ## [0.1.2](https://github.com/maplibre/maplibre-tile-spec/compare/mlt-core-v0.1.1...mlt-core-v0.1.2) - 2026-02-25 ### Added - Release done to test if some of the release automation is working. Should fix `cargo-binstall mlt` ## [0.1.1](https://github.com/maplibre/maplibre-tile-spec/compare/mlt-core-v0.1.0...mlt-core-v0.1.1) - 2026-02-25 ### Added - *(rust)* Stream encoding optimizer ([#950](https://github.com/maplibre/maplibre-tile-spec/pull/950)) - *(rust)* Synthetic generation ([#947](https://github.com/maplibre/maplibre-tile-spec/pull/947)) - *(rust)* FastPFOR encoding support ([#936](https://github.com/maplibre/maplibre-tile-spec/pull/936)) - *(rust)* implement geometry encoding functionality ([#933](https://github.com/maplibre/maplibre-tile-spec/pull/933)) - *(rust)* string encoding support ([#937](https://github.com/maplibre/maplibre-tile-spec/pull/937)) - *(rust)* Point Geometry encoding ([#932](https://github.com/maplibre/maplibre-tile-spec/pull/932)) - *(rust)* Property encoding ([#929](https://github.com/maplibre/maplibre-tile-spec/pull/929)) - *(rust)* impl stream encoding ([#924](https://github.com/maplibre/maplibre-tile-spec/pull/924)) - *(rust)* impl `encode_componentwise_delta_vec2s` ([#930](https://github.com/maplibre/maplibre-tile-spec/pull/930)) - *(rust)* Id encoder implementation ([#906](https://github.com/maplibre/maplibre-tile-spec/pull/906)) ### Fixed - *(rust)* simplify int parsing errors ([#921](https://github.com/maplibre/maplibre-tile-spec/pull/921)) ### Other - *(rust)* improve rust synthetics generation ([#951](https://github.com/maplibre/maplibre-tile-spec/pull/951)) - *(rust)* Reword the docs ([#946](https://github.com/maplibre/maplibre-tile-spec/pull/946)) - *(rust)* more renames ([#945](https://github.com/maplibre/maplibre-tile-spec/pull/945)) - *(rust)* rename encoding->encoder - *(rust)* rename encoding-related types for clarity ([#944](https://github.com/maplibre/maplibre-tile-spec/pull/944)) - *(rust)* optimize some UI code ([#939](https://github.com/maplibre/maplibre-tile-spec/pull/939)) - *(rust)* simplify the proptest strategy ([#935](https://github.com/maplibre/maplibre-tile-spec/pull/935)) - *(rust)* remove unused variables and simplify buffer handling ([#925](https://github.com/maplibre/maplibre-tile-spec/pull/925)) - *(rust)* rename LogicalDecoder and PhysicalDecoder to codecs ([#923](https://github.com/maplibre/maplibre-tile-spec/pull/923)) - *(rust)* improve error handling and messaging in error types ([#920](https://github.com/maplibre/maplibre-tile-spec/pull/920)) - *(rust)* rename `UnsupportedLogicalTechniqueCombination` ([#919](https://github.com/maplibre/maplibre-tile-spec/pull/919)) - *(rust)* Shift id encoding to the stream ([#918](https://github.com/maplibre/maplibre-tile-spec/pull/918)) - *(rust)* add basic benchmark infrastructure ([#912](https://github.com/maplibre/maplibre-tile-spec/pull/912)) - *(rust)* cleanup errors and some namespaces ([#917](https://github.com/maplibre/maplibre-tile-spec/pull/917)) - *(rust)* cleanup around the property mod ([#915](https://github.com/maplibre/maplibre-tile-spec/pull/915)) - *(rust)* more cleanups ([#914](https://github.com/maplibre/maplibre-tile-spec/pull/914)) - *(rust)* rename raw -> encoded ([#910](https://github.com/maplibre/maplibre-tile-spec/pull/910)) - *(rust)* refactor and add plumbing for encoding ([#909](https://github.com/maplibre/maplibre-tile-spec/pull/909)) ================================================ FILE: rust/mlt-core/Cargo.toml ================================================ [package] name = "mlt-core" description = "MapLibre Tile library code" version = "0.9.0" authors.workspace = true categories.workspace = true edition.workspace = true homepage.workspace = true keywords.workspace = true license.workspace = true repository.workspace = true rust-version.workspace = true [[bench]] name = "decoding_e2e" harness = false required-features = ["__private"] [[bench]] name = "encoding_e2e" harness = false required-features = ["__private"] [[bench]] name = "decoding_utils" harness = false required-features = ["__private"] [[bench]] name = "decoding_strings" harness = false required-features = ["__private"] [[bench]] name = "encoding_from_mvt" harness = false required-features = ["__private"] [features] default = [] arbitrary = ["dep:arbitrary", "geo-types/arbitrary"] # Used internally for testing and benchmarking, not intended for public use __private = [] [dependencies] arbitrary = { workspace = true, optional = true } bitvec.workspace = true bytemuck.workspace = true derive-debug.workspace = true enum_dispatch.workspace = true fastpfor.workspace = true fsst-rs.workspace = true geo = { workspace = true, features = ["earcut"] } geo-types.workspace = true hex.workspace = true hilbert_2d.workspace = true hotpath.workspace = true integer-encoding.workspace = true mvt-reader.workspace = true num-traits.workspace = true num_enum.workspace = true probabilistic-collections.workspace = true serde.workspace = true serde_json.workspace = true strum.workspace = true thiserror.workspace = true union-find.workspace = true wide.workspace = true zigzag.workspace = true [dev-dependencies] brotli.workspace = true criterion.workspace = true flate2.workspace = true geo-types = { workspace = true, features = ["arbitrary"] } insta.workspace = true pretty_assertions.workspace = true proptest.workspace = true proptest-derive.workspace = true rstest.workspace = true test_each_file.workspace = true zstd.workspace = true [lints] workspace = true [[test]] name = "geojson" required-features = ["__private"] [[test]] name = "snapshots" required-features = ["__private"] ================================================ FILE: rust/mlt-core/README.md ================================================ # `MapLibre Tile` (MLT) Rust library MapLibre Logo The `MapLibre Tile` specification is mainly inspired by the [Mapbox Vector Tile (MVT)](https://github.com/mapbox/vector-tile-spec) specification, but has been redesigned from the ground up to address the challenges of rapidly growing geospatial data volumes and complex next-generation geospatial source formats as well as to leverage the capabilities of modern hardware and APIs. MLT is specifically designed for modern and next generation graphics APIs to enable high-performance processing and rendering of large (planet-scale) 2D and 2.5 basemaps. In particular, MLT offers the following features: - **Improved compression ratio**: Up to 6x on large encoded tiles, based on a column oriented layout with recursively applied (custom) lightweight encodings. This leads to reduced latency, storage, and egress costs and, in particular, improved cache utilization. - **Better decoding performance**: Fast lightweight encodings which can be used in combination with SIMD/vectorization instructions. - **Support for linear referencing and m-values**: To efficiently support the upcoming next generation source formats such as Overture Maps (`GeoParquet`). - **Support for 3D coordinates**: i.e., elevation - **Support for complex types**: including nested properties, lists, and maps - **Improved processing performance**, based on storage and in-memory formats that are specifically designed for modern GL APIs, allowing for efficient processing on both CPU and GPU. The formats are designed to be loaded into GPU buffers with little or no additional processing. 📝 For a more in-depth exploration of MLT have a look at the [following slides](https://github.com/mactrem/presentations/blob/main/FOSS4G_2024_Europe/FOSS4G_2024_Europe.pdf), watch [this talk](https://www.youtube.com/watch?v=YHcoAFcsES0) or read [this paper](https://dl.acm.org/doi/10.1145/3748636.3763208) by MLT inventor Markus Tremmel. ## Tile Structure Top level structure of a tile is a sequence of layers, where each layer consists of `(size, tag, data)` tuples: - `size: varint` - size of the data block in bytes, including the size of the `tag` field - `tag: varint` - identifies the block type, e.g. `0x01 = feature table v1`, `0x02 = raster layer`, `0x03 = routing table`, etc. We only define `0x01` for now. - `data: u8[]` - the actual data block of the specified size This approach allows us to easily extend the format in the future by adding new block types, while keeping backward compatibility. Parsers can skip unknown block types by reading the `size` and moving forward accordingly. For now, we only define `0x01` for vector layers, and possibly a few more if needed. Note the ordering: `tag` is after the `size` because it is possible to treat it as a single byte for now, until the parser supports more than 127 types. This allows the parser to efficiently skip unknown types without doing more expensive varint parsing. ## Layer 0x01 - MVT compatibility Structure of the data if the `tag` above is 0x01. We should focus this tag on MVT compatibility, offering exactly what we had in MVT, but allowing for a clearly defined set of encodings and other optimizations like tessellation. No new data formats (per vertex data, nested data, 3d geometries, etc.). No extendable encodings - once finalized, 0x01 will only allow what has been specified. This will ensure that if a decoder declares "0x01" support, it will parse every specification-compliant 0x01 layer. For any new features and encodings we will simply use a new tag ID, likely reusing most of the existing encoding/decoding code. - `name: string` - Name of the layer - `columnCount: varint` - Number of columns in the layer - each column is defined as: - `columnType: varint` - same idea as `tag` above, e.g. `1 = id`, `2 = geometry`, `3 = int property`, etc. - TODO... See [`CONTRIBUTING.md`](../CONTRIBUTING.md) for additional pipeline docs. ## Data Pipeline The diagram below shows the full lifecycle of tile data — from raw bytes on the wire, through lazy parsing and optional per-column decoding, to zero-copy iteration or fully owned row-form access, and back to encoded bytes via the columnar encode pipeline. ```mermaid flowchart TB A(["&[u8] — raw tile bytes"]) subgraph DEC ["Decoding"] B["Parser::parse_layers(&[u8]) Create zero-copy views into input bytes for each layer with almost no memory allocations"] C["Layer<Lazy> Tag01(Layer01<Lazy>) | Unknown columns = LazyParsed::Raw(RawStream) zero allocations for column data"] D["decode_all() — or per-column: decode_id() / decode_geometry() / decode_properties() RawStream → physical codec: FastPFor · varint · byte-RLE → logical codec: delta · zigzag · Morton · Hilbert → typed column buffers Vec<T>"] E["ParsedLayer = Layer<Parsed> all columns decoded into typed buffers"] end subgraph ACCESS ["Iterate (zero-copy borrow)"] F["iter_features() → Layer01FeatureIter"] G["FeatureRef id: Option<u64> geometry reference property iterator"] end H(["Layer01::into_tile()"]) subgraph BRIDGE ["TileLayer (row-oriented, fully owned)"] I["Vec<TileFeature> id: Option<u64> geometry: geo_types::Geometry<i32> props: Vec<PropValue>"] end subgraph ENC ["Encoding"] J["StagedLayer::from(TileLayer) owned columnar form IdValues · GeometryValues · Vec<StagedProperty>"] K["StagedLayer::encode() / encode_auto() / encode_with_profile() per-column compression applied"] L["EncodedLayer01 (wire-ready) EncodedId · EncodedGeometry Vec<EncodedProperty> each = Vec<EncodedStream>"] M["EncodedLayer::write_to() serialises: varint(size) + tag byte + column payloads"] end N(["Vec<u8> — encoded tile bytes"]) A --> B --> C --> D --> E E -->|"borrow"| F --> G E -->|"own all data"| H --> I I -->|"From<TileLayer>"| J --> K --> L --> M --> N classDef io fill:#1e5c3a,color:#e8f5e9,stroke:#0a3d22 classDef bridge fill:#4a1c6b,color:#f3e5f5,stroke:#2d0b45 classDef dec fill:#1a3a5c,color:#e3f2fd,stroke:#0d1f35 classDef enc fill:#5c2a1a,color:#fbe9e7,stroke:#3d1510 class A,N io class H,I bridge class B,C,D,E,F,G dec class J,K,L,M enc ``` **Key types and their roles:** | Type | Role | |------|------| | `Layer` / `Layer01` | Parsed frame with column byte slices still unprocessed; zero allocation beyond the parse pass | | `LazyParsed` | Type-state wrapper: `Raw(RawStream)` before decoding, `Parsed(T)` after, `ParsingFailed` on error | | `ParsedLayer` = `Layer` | All columns decoded; borrow-based iteration via `iter_features()` | | `TileLayer` | Row-oriented, fully owned bridge between decode and encode | | `StagedLayer` | Owned columnar data ready for compression/encoding | | `EncodedLayer01` | Wire-ready columnar data; written by `write_to()` with size + tag prefix | ## Tools See the `mlt` tool for various ways to interact with the parser and decoder. This includes a terminal-based visualizer for exploring MLT files. ================================================ FILE: rust/mlt-core/benches/bench_utils.rs ================================================ #![allow(dead_code)] use std::fs; use std::path::Path; // This code runs in CI because of --all-targets, so make it run really fast. #[cfg(debug_assertions)] pub const BENCHMARKED_ZOOM_LEVELS: [u8; 1] = [0]; #[cfg(not(debug_assertions))] pub const BENCHMARKED_ZOOM_LEVELS: [u8; 3] = [4, 7, 13]; /// Recursively walk `dir` and collect all files with the given `extension`. fn walk_dir(dir: &Path, extension: &str, out: &mut Vec<(String, Vec)>) { let entries = fs::read_dir(dir).unwrap_or_else(|err| panic!("can't read {}: {err}", dir.display())); for entry in entries { let entry = entry.unwrap_or_else(|err| panic!("can't read entry in {}: {err}", dir.display())); let path = entry.path(); if path.is_dir() { walk_dir(&path, extension, out); } else { let name = path.to_string_lossy(); if name.ends_with(extension) { let data = fs::read(&path) .unwrap_or_else(|err| panic!("can't read {}: {err}", path.display())); out.push((path.to_string_lossy().into_owned(), data)); } } } } /// Load all `.mvt` files found recursively under `../../test`. /// /// Returns `(path_string, raw_bytes)` pairs sorted by path. /// In debug builds (CI), returns only the first file to keep tests fast. #[must_use] pub fn load_all_mvt_bytes() -> Vec<(String, Vec)> { let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../test"); let mut tiles = Vec::new(); walk_dir(&dir, ".mvt", &mut tiles); assert!( !tiles.is_empty(), "No .mvt files found under {}", dir.display() ); tiles.sort_by(|a, b| a.0.cmp(&b.0)); #[cfg(debug_assertions)] tiles.truncate(1); tiles } #[must_use] pub fn load_mlt_tiles(zoom: u8) -> Vec<(String, Vec)> { load_tiles(zoom, "expected/tag0x01/omt", ".mlt") } #[must_use] pub fn load_tiles(zoom: u8, test_subpath: &str, extension: &str) -> Vec<(String, Vec)> { let dir = Path::new(env!("CARGO_MANIFEST_DIR")) .join("../../test") .join(test_subpath); let prefix = format!("{zoom}_"); let mut tiles = Vec::new(); let entries = fs::read_dir(&dir).unwrap_or_else(|err| panic!("can't read {}: {err}", dir.display())); for entry in entries { let entry = entry.unwrap_or_else(|err| panic!("can't read entry {}: {err}", dir.display())); let file_name = entry.file_name(); let name = file_name.to_string_lossy(); if name.starts_with(&prefix) && let Some(stem) = name.strip_suffix(extension) { let data = fs::read(entry.path()) .unwrap_or_else(|err| panic!("can't read {}: {err}", entry.path().display())); tiles.push((stem.to_string(), data)); } } assert!( !tiles.is_empty(), "No tiles found for zoom level {zoom} in {}", dir.display() ); tiles.sort_by(|a, b| a.0.cmp(&b.0)); tiles } #[must_use] pub fn total_bytes(tiles: &[(String, Vec)]) -> usize { tiles.iter().map(|(_, d)| d.len()).sum() } ================================================ FILE: rust/mlt-core/benches/decoding_e2e.rs ================================================ use std::hint::black_box; use std::io::{Read as _, Write as _}; use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; use mlt_core::__private::{dec, parser}; #[path = "bench_utils.rs"] mod bench_utils; use bench_utils::{BENCHMARKED_ZOOM_LEVELS, load_mlt_tiles, load_tiles, total_bytes}; fn load_proto_tiles(zoom: u8) -> Vec<(String, Vec)> { load_tiles(zoom, "fixtures/omt", ".mvt") } fn compress_gzip(data: &[u8]) -> Vec { let mut encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default()); encoder.write_all(data).expect("gzip compress failed"); encoder.finish().expect("gzip finish failed") } fn decompress_gzip(data: &[u8]) -> Vec { let mut decoder = flate2::read::GzDecoder::new(data); let mut out = Vec::new(); decoder .read_to_end(&mut out) .expect("gzip decompress failed"); out } fn compress_zstd(data: &[u8]) -> Vec { zstd::encode_all(data, 3).expect("zstd compress failed") } fn decompress_zstd(data: &[u8]) -> Vec { zstd::decode_all(data).expect("zstd decompress failed") } fn compress_brotli(data: &[u8]) -> Vec { let mut out = Vec::new(); // quality 6, lgwin 22 (brotli defaults) let mut encoder = brotli::CompressorWriter::new(&mut out, 4096, 6, 22); encoder.write_all(data).expect("brotli compress failed"); drop(encoder); out } fn decompress_brotli(data: &[u8]) -> Vec { let mut out = Vec::new(); let mut decoder = brotli::Decompressor::new(data, 4096); decoder .read_to_end(&mut out) .expect("brotli decompress failed"); out } fn compress_tiles( tiles: &[(String, Vec)], compress: fn(&[u8]) -> Vec, ) -> Vec<(String, Vec)> { tiles .iter() .map(|(name, data)| (name.clone(), compress(data))) .collect() } fn mvt_parse(data: Vec) { let reader = mvt_reader::Reader::new(black_box(data)).expect("mvt reader construction failed"); let _ = black_box(reader); } fn mvt_decode(data: Vec) { let reader = mvt_reader::Reader::new(black_box(data)).expect("mvt reader construction failed"); let layers = reader .get_layer_metadata() .expect("mvt layer metadata failed"); for layer in &layers { let features = reader .get_features(layer.layer_index) .expect("mvt get_features failed"); let _ = black_box(features); } let _ = black_box(reader); } type Codec = (&'static str, fn(&[u8]) -> Vec, fn(&[u8]) -> Vec); fn identity(data: &[u8]) -> Vec { data.to_vec() } const CODECS: &[Codec] = &[ ("none", identity, identity), ("gzip", compress_gzip, decompress_gzip), ("zstd", compress_zstd, decompress_zstd), ("brotli", compress_brotli, decompress_brotli), ]; fn bench_parse(c: &mut Criterion) { let mut group = c.benchmark_group("parse"); for zoom in BENCHMARKED_ZOOM_LEVELS { let mlt_tiles = load_mlt_tiles(zoom); let proto_tiles = load_proto_tiles(zoom); // mlt parse group.throughput(Throughput::Bytes(total_bytes(&mlt_tiles) as u64)); group.bench_with_input(BenchmarkId::new("mlt", zoom), &mlt_tiles, |b, tiles| { b.iter(|| { for (_, data) in tiles { black_box( parser() .parse_layers(black_box(data)) .expect("mlt parse failed"), ); } }); }); // mvt parse (per codec) for &(codec_name, compress, decompress) in CODECS { let compressed = compress_tiles(&proto_tiles, compress); group.throughput(Throughput::Bytes(total_bytes(&compressed) as u64)); group.bench_with_input( BenchmarkId::new(format!("mvt+{codec_name}"), zoom), &compressed, |b, tiles| { b.iter_batched( || tiles.iter().map(|(_, d)| d.clone()).collect::>(), |compressed_data| { for data in compressed_data { mvt_parse(decompress(black_box(&data))); } }, BatchSize::LargeInput, ); }, ); } } group.finish(); } fn bench_decode_all(c: &mut Criterion) { let mut group = c.benchmark_group("decode_all"); for zoom in BENCHMARKED_ZOOM_LEVELS { let mlt_tiles = load_mlt_tiles(zoom); let proto_tiles = load_proto_tiles(zoom); // mlt decode_all group.throughput(Throughput::Bytes(total_bytes(&mlt_tiles) as u64)); group.bench_with_input(BenchmarkId::new("mlt", zoom), &mlt_tiles, |b, tiles| { b.iter_batched( || { tiles .iter() .map(|(_, v)| { parser() .parse_layers(black_box(v)) .expect("mlt parse failed") }) .collect::>() }, |mlt| { let mut d = dec(); let decoded: Vec> = mlt .into_iter() .map(|layers| { d.reset_budget(); let dec_tile = d.decode_all(layers).expect("mlt decode_all failed"); black_box(dec_tile) }) .collect(); black_box(decoded); }, BatchSize::SmallInput, ); }); // mvt decode_all (per codec) for &(codec_name, compress, decompress) in CODECS { let compressed = compress_tiles(&proto_tiles, compress); group.throughput(Throughput::Bytes(total_bytes(&compressed) as u64)); group.bench_with_input( BenchmarkId::new(format!("mvt+{codec_name}"), zoom), &compressed, |b, tiles| { b.iter_batched( || tiles.iter().map(|(_, d)| d.clone()).collect::>(), |compressed_data| { for data in compressed_data { mvt_decode(decompress(black_box(&data))); } }, BatchSize::LargeInput, ); }, ); } } group.finish(); } criterion_group!(benches, bench_parse, bench_decode_all); criterion_main!(benches); ================================================ FILE: rust/mlt-core/benches/decoding_strings.rs ================================================ use std::hint::black_box; use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; use geo_types::Point; use mlt_core::encoder::{ Codecs, Encoder, EncoderConfig, ExplicitEncoder, IntEncoder, LogicalEncoder, PhysicalEncoder, Presence, StagedId, StagedLayer, StagedProperty, StagedSharedDict, StrEncoding, }; use mlt_core::test_helpers::{dec, parser}; use mlt_core::{GeometryValues, LendingIterator, ParsedLayer01, PropValueRef}; use strum::IntoEnumIterator as _; // This code runs in CI because of --all-targets, so make it run really fast. #[cfg(debug_assertions)] pub const BENCHMARKED_LENGTHS: [usize; 1] = [1]; #[cfg(not(debug_assertions))] pub const BENCHMARKED_LENGTHS: [usize; 6] = [1, 20, 64, 256, 1024, 2048]; fn limit(values: impl Iterator) -> impl Iterator { if cfg!(debug_assertions) { values.take(1) } else { values.take(usize::MAX) } } /// Generate a mix of strings with repetition so dictionary encoding has something to compress. /// The vocabulary is small relative to N, so values repeat often. fn make_strings(n: usize) -> Vec { const VOCAB: &[&str] = &[ "highway", "residential", "motorway", "primary", "secondary", "tertiary", "water", "forest", "park", "building", "amenity", "shop", "landuse", "natural", "place", "boundary", ]; black_box( (0..n) .map(|i| { let idx = i % VOCAB.len(); if i.is_multiple_of(4) { VOCAB[idx].to_string() } else { format!("{}_{}", VOCAB[idx], i % 32) } }) .collect(), ) } /// Same pool as `make_strings`, but every 5th entry is `None` so the presence /// stream has real work to do. fn make_nullable_strings(n: usize) -> Vec> { black_box( make_strings(n) .into_iter() .enumerate() .map(|(i, s)| if i.is_multiple_of(5) { None } else { Some(s) }) .collect(), ) } /// Build `n` degenerate point features at the origin for use as layer geometry. fn make_geometry(n: usize) -> GeometryValues { let mut g = GeometryValues::default(); for _ in 0..n { g.push_geom(&geo_types::Geometry::::Point(Point::new(0, 0))); } g } /// Encode `props` into a single-layer tile with `n` point features and return wire bytes. fn encode_layer(n: usize, props: Vec, cfg: ExplicitEncoder) -> Vec { let mut codecs = Codecs::default(); StagedLayer { name: "bench".into(), extent: 4096, id: StagedId::None, geometry: make_geometry(n), properties: props, } .encode_into( Encoder::with_explicit(EncoderConfig::default(), cfg), &mut codecs, ) .expect("encode_layer failed") .into_layer_bytes() .expect("into_layer_bytes failed") } /// Sum the byte lengths of all non-null string property values across all features. /// /// Used as the benchmark measurement: the return value prevents the compiler from /// optimizing away the iteration, and its magnitude is proportional to work done. fn sum_str_lens(parsed: &ParsedLayer01<'_>) -> usize { let mut total = 0; let mut iter = parsed.iter_features(); while let Some(feat_res) = iter.next() { total += feat_res .unwrap() .iter_all_properties() .map(|v| { if let Some(PropValueRef::Str(s)) = v { s.len() } else { 0 } }) .sum::(); } total } /// plain strings: vary the `IntEncoder` used for the length stream fn bench_plain_length_encoding(c: &mut Criterion) { let mut group = c.benchmark_group("strings/plain/length_enc"); for n in BENCHMARKED_LENGTHS { let col = make_strings(n); group.throughput(Throughput::Elements(n as u64)); for logical in limit(LogicalEncoder::iter()) { for physical in limit(PhysicalEncoder::iter()) { let int_enc = IntEncoder::new(logical, physical); let bytes = encode_layer( n, vec![StagedProperty::str("name", col.clone())], ExplicitEncoder::all(int_enc), ); group.bench_with_input( BenchmarkId::new(format!("{logical:?}-{physical:?}"), n), &bytes, |b, bytes| { b.iter(|| { let layer = parser() .parse_layers(bytes) .expect("parse") .remove(0) .into_layer01() .expect("layer01"); let parsed = layer.decode_all(&mut dec()).expect("decode_all"); black_box(sum_str_lens(&parsed)) }); }, ); } } } group.finish(); } /// FSST strings: vary the `IntEncoder` used for the symbol-length and value-length streams fn bench_fsst_length_encoding(c: &mut Criterion) { let mut group = c.benchmark_group("strings/fsst/length_enc"); for n in BENCHMARKED_LENGTHS { let col = make_strings(n); group.throughput(Throughput::Elements(n as u64)); for logical in limit(LogicalEncoder::iter()) { for physical in limit(PhysicalEncoder::iter()) { let int_enc = IntEncoder::new(logical, physical); let bytes = encode_layer( n, vec![StagedProperty::str("name", col.clone())], ExplicitEncoder::all_with_str(int_enc, StrEncoding::Fsst), ); group.bench_with_input( BenchmarkId::new(format!("{logical:?}-{physical:?}"), n), &bytes, |b, bytes| { b.iter(|| { let layer = parser() .parse_layers(bytes) .expect("parse") .remove(0) .into_layer01() .expect("layer01"); let parsed = layer.decode_all(&mut dec()).expect("decode_all"); black_box(sum_str_lens(&parsed)) }); }, ); } } } group.finish(); } /// Benchmark 3 – encoding type: plain vs FSST, fixed `IntEncoder` fn bench_encoding_type(c: &mut Criterion) { let mut group = c.benchmark_group("strings/encoding_type"); let int_enc = IntEncoder::plain(); for n in BENCHMARKED_LENGTHS { let col = make_strings(n); group.throughput(Throughput::Elements(n as u64)); let plain_bytes = encode_layer( n, vec![StagedProperty::str("name", col.clone())], ExplicitEncoder::all(int_enc), ); group.bench_with_input(BenchmarkId::new("plain", n), &plain_bytes, |b, bytes| { b.iter(|| { let layer = parser() .parse_layers(bytes) .expect("parse") .remove(0) .into_layer01() .expect("layer01"); let parsed = layer.decode_all(&mut dec()).expect("decode_all"); black_box(sum_str_lens(&parsed)) }); }); let fsst_bytes = encode_layer( n, vec![StagedProperty::str("name", col)], ExplicitEncoder::all_with_str(int_enc, StrEncoding::Fsst), ); group.bench_with_input(BenchmarkId::new("fsst", n), &fsst_bytes, |b, bytes| { b.iter(|| { let layer = parser() .parse_layers(bytes) .expect("parse") .remove(0) .into_layer01() .expect("layer01"); let parsed = layer.decode_all(&mut dec()).expect("decode_all"); black_box(sum_str_lens(&parsed)) }); }); } group.finish(); } /// Benchmark 4 – presence stream overhead: non-nullable vs nullable column fn bench_presence(c: &mut Criterion) { let mut group = c.benchmark_group("strings/presence"); let int_enc = IntEncoder::plain(); for n in BENCHMARKED_LENGTHS { group.throughput(Throughput::Elements(n as u64)); // Non-nullable: no presence stream emitted. let no_null_bytes = encode_layer( n, vec![StagedProperty::str("name", make_strings(n))], ExplicitEncoder::all(int_enc), ); group.bench_with_input( BenchmarkId::new("no_nulls", n), &no_null_bytes, |b, bytes| { b.iter(|| { let layer = parser() .parse_layers(bytes) .expect("parse") .remove(0) .into_layer01() .expect("layer01"); let parsed = layer.decode_all(&mut dec()).expect("decode_all"); black_box(sum_str_lens(&parsed)) }); }, ); // Nullable: presence stream present, every 5th entry is None. let null_bytes = encode_layer( n, vec![StagedProperty::opt_str("name", make_nullable_strings(n))], ExplicitEncoder::all(int_enc), ); group.bench_with_input( BenchmarkId::new("with_nulls", n), &null_bytes, |b, bytes| { b.iter(|| { let layer = parser() .parse_layers(bytes) .expect("parse") .remove(0) .into_layer01() .expect("layer01"); let parsed = layer.decode_all(&mut dec()).expect("decode_all"); black_box(sum_str_lens(&parsed)) }); }, ); } group.finish(); } /// Benchmark 5 – shared dict vs plain /// /// Compares decoding two plain string columns against a shared-dictionary struct /// column (plain and FSST flavors) that carries the same string data spread /// across two child sub-properties. /// Throughput is reported per *logical* string entry so all variants are comparable. fn bench_vs_shared_dict(c: &mut Criterion) { let mut group = c.benchmark_group("strings/vs_shared_dict"); let int_enc = IntEncoder::plain(); for n in BENCHMARKED_LENGTHS { let total_entries = n * 2; group.throughput(Throughput::Elements(total_entries as u64)); let col = make_strings(n); let col_opt: Vec> = col.iter().map(|s| Some(s.clone())).collect(); // --- plain: two independent string columns --- let plain_x2_bytes = encode_layer( n, vec![ StagedProperty::str("col1", col.clone()), StagedProperty::str("col2", col.clone()), ], ExplicitEncoder::all(int_enc), ); group.bench_with_input( BenchmarkId::new("plain_x2", n), &plain_x2_bytes, |b, bytes| { b.iter(|| { let layer = parser() .parse_layers(bytes) .expect("parse") .remove(0) .into_layer01() .expect("layer01"); let parsed = layer.decode_all(&mut dec()).expect("decode_all"); black_box(sum_str_lens(&parsed)) }); }, ); // --- shared dict (plain) --- // // Two sub-properties; the second child has every 3rd entry as NULL so // the child presence path is exercised. let col2: Vec> = col .iter() .enumerate() .map(|(i, s)| if i % 3 == 0 { None } else { Some(s.clone()) }) .collect(); let make_sd = || { StagedSharedDict::new( "place:", [ ("type", col_opt.clone(), Presence::AllPresent), ("subtype", col2.clone(), Presence::Mixed), ], ) .expect("StagedSharedDict::new failed") }; let sd_plain_bytes = encode_layer( n, vec![StagedProperty::SharedDict(make_sd())], ExplicitEncoder::all(int_enc), ); group.bench_with_input( BenchmarkId::new("shared_dict_plain", n), &sd_plain_bytes, |b, bytes| { b.iter(|| { let layer = parser() .parse_layers(bytes) .expect("parse") .remove(0) .into_layer01() .expect("layer01"); let parsed = layer.decode_all(&mut dec()).expect("decode_all"); black_box(sum_str_lens(&parsed)) }); }, ); // --- shared dict (FSST) --- let sd_fsst_bytes = encode_layer( n, vec![StagedProperty::SharedDict(make_sd())], ExplicitEncoder::all_with_str(int_enc, StrEncoding::Fsst), ); group.bench_with_input( BenchmarkId::new("shared_dict_fsst", n), &sd_fsst_bytes, |b, bytes| { b.iter(|| { let layer = parser() .parse_layers(bytes) .expect("parse") .remove(0) .into_layer01() .expect("layer01"); let parsed = layer.decode_all(&mut dec()).expect("decode_all"); black_box(sum_str_lens(&parsed)) }); }, ); // --- FSST plain: two independent FSST-encoded columns --- let fsst_x2_bytes = encode_layer( n, vec![ StagedProperty::str("col1", col.clone()), StagedProperty::str("col2", col), ], ExplicitEncoder::all_with_str(int_enc, StrEncoding::Fsst), ); group.bench_with_input( BenchmarkId::new("fsst_x2", n), &fsst_x2_bytes, |b, bytes| { b.iter(|| { let layer = parser() .parse_layers(bytes) .expect("parse") .remove(0) .into_layer01() .expect("layer01"); let parsed = layer.decode_all(&mut dec()).expect("decode_all"); black_box(sum_str_lens(&parsed)) }); }, ); } group.finish(); } criterion_group!( benches, bench_plain_length_encoding, bench_fsst_length_encoding, bench_encoding_type, bench_presence, bench_vs_shared_dict, ); criterion_main!(benches); ================================================ FILE: rust/mlt-core/benches/decoding_utils.rs ================================================ use std::hint::black_box; use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; use mlt_core::Decoder; use mlt_core::wire::Morton; const NUM_BITS: u32 = 15; const COORDINATE_SHIFT: u32 = 1 << (NUM_BITS - 1); // This code runs in CI because of --all-targets, so make it run really fast. #[cfg(debug_assertions)] pub const BENCHMARKED_LENGTHS: [u32; 1] = [1]; #[cfg(not(debug_assertions))] pub const BENCHMARKED_LENGTHS: [u32; 3] = [64, 256, 1024]; /// Interleave `x` and `y` into a single Morton code using 15 bits per component. /// /// Even bit positions encode `x`, odd positions encode `y`. /// This is the inverse of [`Morton::decode_codes`] / [`Morton::decode_delta`]. #[must_use] #[inline] pub fn encode_morton_15(x: u32, y: u32) -> u32 { let mut code = 0u32; for bit in 0..15 { code |= ((x >> bit) & 1) << (2 * bit); code |= ((y >> bit) & 1) << (2 * bit + 1); } code } fn make_morton_codes(n: u32) -> Vec { (0..n) .map(|i| { let x = (i * 7 + 13) & 0x7FFF; let y = (i * 11 + 31) & 0x7FFF; encode_morton_15(x, y) }) .collect() } fn make_morton_deltas(n: u32) -> Vec { let codes = make_morton_codes(n); let mut prev = 0i32; codes .iter() .map(|&c| { let delta = c.cast_signed().wrapping_sub(prev).cast_unsigned(); prev = c.cast_signed(); delta }) .collect() } fn bench_impls( c: &mut Criterion, group_name: &str, make_input: impl Fn(u32) -> Vec, imp: impl Fn(&[I]) -> O, ) { let mut group = c.benchmark_group(group_name); for n in BENCHMARKED_LENGTHS { let input = make_input(n); group.throughput(Throughput::Elements(u64::from(n))); group.bench_with_input(BenchmarkId::new("impl", n), &input, |b, input| { b.iter_batched( || input.clone(), |i| black_box(imp(&i)), BatchSize::SmallInput, ); }); } group.finish(); } fn bench_morton(c: &mut Criterion) { let meta = Morton::new(NUM_BITS, COORDINATE_SHIFT).unwrap(); bench_impls(c, "morton/decode_codes", make_morton_codes, |v| { meta.decode_codes(v, &mut Decoder::with_max_size(u32::MAX)) .unwrap() }); bench_impls(c, "morton/decode_delta", make_morton_deltas, |v| { meta.decode_delta(v, &mut Decoder::with_max_size(u32::MAX)) .unwrap() }); } criterion_group!(benches, bench_morton); criterion_main!(benches); ================================================ FILE: rust/mlt-core/benches/encoding_e2e.rs ================================================ use std::hint::black_box; use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; use mlt_core::__private::{dec, parser}; use mlt_core::Layer; use mlt_core::encoder::{EncoderConfig, LogicalEncoder, stage_tile}; use strum::IntoEnumIterator as _; #[path = "bench_utils.rs"] mod bench_utils; use bench_utils::{BENCHMARKED_ZOOM_LEVELS, load_mlt_tiles}; use mlt_core::encoder::SortStrategy::Unsorted; use mlt_core::encoder::{ Codecs, Encoder, ExplicitEncoder, IntEncoder, PhysicalEncoder, StagedLayer, }; fn limit(values: impl Iterator) -> impl Iterator { if cfg!(debug_assertions) { values.take(1) } else { values.take(usize::MAX) } } /// Build `StagedLayer` values from decoded tiles for encode benchmarks. /// /// Goes through `Layer01 → TileLayer → StagedLayer`, which is the correct /// encode-pipeline entry point per CONTRIBUTING.md. fn decode_to_owned(tiles: &[(String, Vec)], tessellate: bool) -> Vec { tiles .iter() .flat_map(|(_, data)| { let mut d = dec(); let layers = parser().parse_layers(data).expect("mlt parse failed"); layers .into_iter() .filter_map(|layer| { let Layer::Tag01(layer01) = layer else { return None; }; let tile = layer01.into_tile(&mut d).ok()?; Some(stage_tile(tile, Unsorted, false, tessellate)) }) .collect::>() }) .collect() } fn bench_encode(c: &mut Criterion) { let mut group = c.benchmark_group("mlt encode"); for zoom in BENCHMARKED_ZOOM_LEVELS { let tiles = load_mlt_tiles(zoom); let total_bytes: usize = tiles.iter().map(|(_, d)| d.len()).sum(); group.throughput(Throughput::Bytes(total_bytes as u64)); for tessellate in [true, false] { let enc_config = EncoderConfig { tessellate, ..Default::default() }; for physical in limit(PhysicalEncoder::iter()) { for logical in limit(LogicalEncoder::iter()) { let int_enc = IntEncoder::new(logical, physical); group.bench_with_input( BenchmarkId::new( format!("{logical:?}-{physical:?}/tessellate: {tessellate:?}"), zoom, ), &tiles, |b, tiles| { b.iter_batched( || decode_to_owned(tiles, enc_config.tessellate), |layers| { let mut codecs = Codecs::default(); for layer in layers { let enc = Encoder::with_explicit( enc_config, ExplicitEncoder::all(int_enc), ); black_box( layer .encode_into(enc, &mut codecs) .expect("encode failed"), ); } }, BatchSize::SmallInput, ); }, ); } } } } group.finish(); } criterion_group!(benches, bench_encode); criterion_main!(benches); ================================================ FILE: rust/mlt-core/benches/encoding_from_mvt.rs ================================================ use std::hint::black_box; use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; use mlt_core::encoder::EncoderConfig; use mlt_core::mvt::mvt_to_tile_layers; use mlt_core::{MltResult, TileLayer}; #[path = "bench_utils.rs"] mod bench_utils; use bench_utils::{BENCHMARKED_ZOOM_LEVELS, load_tiles}; /// Load MVT tiles for a given zoom level from the OMT fixture directory. fn load_mvt_tiles(zoom: u8) -> Vec<(String, Vec)> { load_tiles(zoom, "fixtures/omt", ".mvt") } /// Parse MVT bytes into `TileLayer` objects outside the benchmark loop. /// /// Errors are silently skipped (some fixture tiles may use unsupported geometry /// types); the benchmark only exercises successfully parsed layers. fn parse_mvt_to_tile_layers(mvt_files: &[(String, Vec)]) -> Vec { mvt_files .iter() .flat_map(|(path, data)| { mvt_to_tile_layers(data.clone()).unwrap_or_else(|err| { eprintln!("skipping {path}: {err}"); Vec::new() }) }) .collect() } fn bench_encode_from_mvt(c: &mut Criterion) { let mut group = c.benchmark_group("mlt encode from mvt"); let cfg = EncoderConfig { tessellate: true, ..Default::default() }; for zoom in BENCHMARKED_ZOOM_LEVELS { let mvt_files = load_mvt_tiles(zoom); let total_bytes: usize = mvt_files.iter().map(|(_, d)| d.len()).sum(); // Parse all MVT files into TileLayer once, outside every benchmark iteration. let tile_layers: Vec = parse_mvt_to_tile_layers(&mvt_files); group.throughput(Throughput::Bytes(total_bytes as u64)); group.bench_with_input(BenchmarkId::new("zoom", zoom), &tile_layers, |b, layers| { b.iter_batched( // Setup: clone pre-parsed layers so encode (which takes `self`) can consume them. || layers.clone(), // Benchmark: encode every layer. |layers| { let result: MltResult>> = layers .into_iter() .map(|layer| layer.encode(black_box(cfg))) .collect(); black_box(result.expect("encode failed")); }, BatchSize::LargeInput, ); }); } group.finish(); } criterion_group!(benches, bench_encode_from_mvt); criterion_main!(benches); ================================================ FILE: rust/mlt-core/fuzz/.gitignore ================================================ target corpus artifacts coverage Cargo.lock ================================================ FILE: rust/mlt-core/fuzz/Cargo.toml ================================================ [package] name = "fuzz" version = "0.0.0" publish = false edition = "2024" [package.metadata] cargo-fuzz = true [workspace] [lib] name = "mlt_fuzz" path = "src/lib.rs" [[bin]] name = "layer" path = "fuzz_targets/layer.rs" test = false doc = false bench = false [[bin]] name = "decoded_layer" path = "fuzz_targets/decoded_layer.rs" test = false doc = false bench = false [dependencies] arbitrary = { version = "1.4.2", features = ["derive"] } hex = "0.4.3" libfuzzer-sys = "0.4" mlt-core = { path = "..", features = ["arbitrary", "__private"] } pretty_assertions = "1.4" [lints.rust] unsafe_code = "forbid" unused_qualifications = "warn" unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } [lints.clippy] cargo = { level = "warn", priority = -1 } pedantic = { level = "warn", priority = -1 } # Restrictions panic_in_result_fn = "warn" todo = "warn" unimplemented = "warn" unused_trait_names = "warn" # Too noisy missing_errors_doc = "allow" missing_panics_doc = "allow" multiple_crate_versions = "allow" too_many_lines = "allow" ================================================ FILE: rust/mlt-core/fuzz/README.md ================================================ # Fuzzing for mlt This directory contains fuzzing infrastructure for the `mlt` parser using [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) and [libFuzzer](https://llvm.org/docs/LibFuzzer.html). ## Overview Fuzzing is a software testing technique that provides random or semi-random data as input to find bugs, crashes, and security vulnerabilities. This fuzzer tests the round-trip property of the MapLibre Tile parser: Any data that can be successfully parsed should be serializable back to bytes that match the original input. ## Subproject Structure ``` fuzz/ ├── src/ │ └── lib.rs # mlt_fuzz library with LayerInput and fuzz_roundtrip logic ├── fuzz_targets/ │ └── layer.rs # Fuzz target that feeds random data to LayerInput ├── tests/ │ └── reproduce.rs # Template for reproducing fuzzer-found issues ├── corpus/layer/ # Seed inputs for fuzzing └── artifacts/ # Crash-inducing inputs discovered by fuzzer ``` ## What is Being Tested The fuzzer validates the following property: ``` parse(bytes) -> Layer -> write_to(buffer) -> bytes' assert_eq!(bytes, bytes') ``` This ensures that: 1. The parser correctly interprets the binary format 2. The serializer produces canonical output 3. No data is lost or corrupted during the parse → serialize round-trip ## Prerequisites Install `cargo-fuzz`: ```bash cargo install cargo-fuzz ``` > [!NOTE] > `cargo-fuzz` requires a nightly Rust toolchain. ## Running the Fuzzer From the `fuzz` directory: ```bash cargo +nightly fuzz run layer ``` Popular options: - Run count: `-- -runs=1000000` - Timeout per input: `-- -timeout=10` ## Fuzz Targets ### `layer` **Location:** `fuzz_targets/layer.rs` Tests the `Layer` parser and serializer by generating arbitrary `LayerInput` values and calling `fuzz_roundtrip()` on them. If a mismatch is found, the fuzzer panics with a detailed error message showing both the input and output in hexadecimal format. ## Corpus The `corpus/layer` directory contains input files that have been discovered during fuzzing. These serve as: - Seeds for future fuzzing runs - Regression tests to ensure previously found issues don't reoccur - Examples of valid inputs The corpus is currently empty but will be populated as fuzzing discovers interesting inputs. ## Artifacts When the fuzzer discovers a crash or failure, it saves the triggering input to the `artifacts` directory. Each artifact file contains the exact byte sequence that caused the issue. ## Reproducing Failures When the fuzzer finds an issue, you can reproduce it using the test infrastructure: 1. The failing input is saved to `artifacts/layer/crash-` 2. Minimize the input using `cargo fuzz tmin layer artifacts/layer/crash-` 3. Edit `tests/reproduce.rs` and update the filename: ```rust,no_compile let bytes = include_bytes!("../artifacts/layer/minimized-from-"); ``` 4. Run the test: ```bash cargo test ``` This approach allows you to: - Debug the issue with full Rust tooling (not just nightly) - Set breakpoints and use a debugger - Iterate on fixes quickly Alternatively, you can run the fuzzer directly with the artifact: ```bash cargo +nightly fuzz run layer artifacts/layer/crash- ``` ## Coverage To generate coverage information: ```bash cargo +nightly fuzz coverage layer ``` This creates coverage data in the `coverage` directory, showing which code paths have been exercised during fuzzing. ## Adding New Fuzz Targets To add a new fuzz target: 1. Create a new file in `fuzz_targets/`: ```bash cargo fuzz add ``` 2. Implement the fuzz target using `fuzz_target!` macro 3. Add corresponding test infrastructure in `src/lib.rs` and `tests/` ## Further Reading - [cargo-fuzz documentation](https://rust-fuzz.github.io/book/cargo-fuzz.html) - [libFuzzer documentation](https://llvm.org/docs/LibFuzzer.html) - [mlt library documentation](../README.md) ================================================ FILE: rust/mlt-core/fuzz/fuzz_targets/decoded_layer.rs ================================================ #![no_main] use libfuzzer_sys::fuzz_target; use mlt_fuzz::DecodedLayerInput; fuzz_target!(|input: DecodedLayerInput| { input.fuzz_roundtrip(); }); ================================================ FILE: rust/mlt-core/fuzz/fuzz_targets/layer.rs ================================================ #![no_main] use libfuzzer_sys::fuzz_target; use mlt_fuzz::LayerInput; fuzz_target!(|input: LayerInput| { input.fuzz_roundtrip(); }); ================================================ FILE: rust/mlt-core/fuzz/src/decoded_layer.rs ================================================ use mlt_core::encoder::SortStrategy::Unsorted; use mlt_core::encoder::{Codecs, Encoder, StagedLayer, stage_tile}; use mlt_core::{Decoder, Layer, Parser, TileLayer}; /// Fuzz input that starts from a staged layer and tests encode → decode roundtrip. /// /// Generates valid [`StagedLayer`] values directly and verifies that the /// canonical roundtrip (`Tile -> Staged -> bytes -> Tile`) is idempotent. pub struct DecodedLayerInput { pub layer: StagedLayer, } impl arbitrary::Arbitrary<'_> for DecodedLayerInput { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let layer: StagedLayer = u.arbitrary()?; Ok(Self { layer }) } } impl DecodedLayerInput { pub fn fuzz_roundtrip(self) { // Normalize: encode the fuzzed StagedLayer and decode to TileLayer. // This drops all-null columns, etc. — expected encoder behavior. let tile1 = encode_decode(self.layer); let tile2 = encode_decode(stage_tile(tile1, Unsorted, false, false)); // Same roundtrip again — must be a fixpoint. let tile3 = encode_decode(stage_tile(tile2.clone(), Unsorted, false, false)); assert_eq!(tile2, tile3, "canonical roundtrip is not idempotent"); } } /// Encode a [`StagedLayer`] to bytes, then parse and decode back to a /// row-oriented [`TileLayer`]. fn encode_decode(staged: StagedLayer) -> TileLayer { let mut codecs = Codecs::default(); let buffer = staged .encode_into(Encoder::default(), &mut codecs) .expect("encode should not fail") .into_layer_bytes() .expect("into_layer_bytes should not fail"); let mut layers = Parser::default() .parse_layers(&buffer) .expect("layer must re-parse"); assert_eq!(layers.len(), 1, "expected exactly one layer"); let Layer::Tag01(lazy) = layers.remove(0) else { panic!("expected Tag01 layer"); }; lazy.into_tile(&mut Decoder::default()) .expect("into_tile should not fail") } impl std::fmt::Debug for DecodedLayerInput { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "DecodedLayerInput {{\n\tlayer: {:#?}\n}}", self.layer) } } ================================================ FILE: rust/mlt-core/fuzz/src/layer.rs ================================================ use hex::ToHex as _; use mlt_core::{Decoder, Parser}; #[derive(arbitrary::Arbitrary)] pub struct LayerInput { pub bytes: Vec, } impl std::fmt::Debug for LayerInput { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self.bytes.encode_hex::()) } } impl LayerInput { pub fn fuzz_roundtrip(self) { let Ok(layers) = Parser::default().parse_layers(&self.bytes) else { return; }; for layer in layers { let _ = layer.decode_all(&mut Decoder::default()); } } } ================================================ FILE: rust/mlt-core/fuzz/src/lib.rs ================================================ mod decoded_layer; mod layer; pub use decoded_layer::*; pub use layer::*; ================================================ FILE: rust/mlt-core/fuzz/tests/reproduce.rs ================================================ use mlt_fuzz::LayerInput; /// Template for reproducing fuzzer-found issues /// /// When the fuzzer finds a crash, replace the filename below with the artifact path: /// 1. Update the filename: `../artifacts/layer/crash-abc123` /// 2. Run: `cargo test --manifest-path fuzz/Cargo.toml` #[test] fn fuzz_roundtrip() { let bytes = include_bytes!("../artifacts/layer/crash-abc123"); let input = LayerInput { bytes: bytes.to_vec(), }; input.fuzz_roundtrip(); } ================================================ FILE: rust/mlt-core/src/codecs/bytes.rs ================================================ use crate::MltError::BufferUnderflow; use crate::utils::{AsUsize as _, take}; use crate::{Decoder, MltRefResult, MltResult}; /// Pack bools into bytes where each byte represents 8 booleans. pub fn encode_bools_to_bytes( bools: impl ExactSizeIterator, target: &mut Vec, ) -> &[u8] { let num_bytes = bools.len().div_ceil(8); target.clear(); target.resize(num_bytes, 0u8); for i in bools.enumerate().filter_map(|(i, bit)| bit.then_some(i)) { target[i / 8] |= 1 << (i % 8); } target } /// Decode a slice of bytes into a vector of u64 values assuming little-endian encoding /// TODO: Should this return `MltRefResult`, or should it assert the entire input is consumed? pub fn decode_bytes_to_u64s<'a>( mut input: &'a [u8], num_values: u32, dec: &mut Decoder, ) -> MltRefResult<'a, Vec> { let Some(expected_bytes) = num_values.checked_mul(8) else { return Err(BufferUnderflow(u32::MAX, input.len())); }; if input.len() < expected_bytes.as_usize() { return Err(BufferUnderflow(expected_bytes, input.len())); } let alloc_size = num_values.as_usize(); let mut values = dec.alloc(alloc_size)?; for _ in 0..num_values { let (new_input, bytes) = take(input, 8)?; let value = u64::from_le_bytes([ bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], ]); values.push(value); input = new_input; } debug_assert_length(&values, alloc_size); Ok((input, values)) } /// Decode a slice of bytes into a vector of u32 values assuming little-endian encoding /// FIXME: ensure the entire input is consumed, and don't return it? pub fn decode_bytes_to_u32s<'a>( mut input: &'a [u8], num_values: u32, dec: &mut Decoder, ) -> MltRefResult<'a, Vec> { let Some(expected_bytes) = num_values.checked_mul(4) else { return Err(BufferUnderflow(u32::MAX, input.len())); }; if input.len() < expected_bytes.as_usize() { return Err(BufferUnderflow(expected_bytes, input.len())); } let alloc_size = num_values.as_usize(); let mut values = dec.alloc(alloc_size)?; for _ in 0..num_values { let (new_input, bytes) = take(input, 4)?; let value = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); values.push(value); input = new_input; } debug_assert_length(&values, alloc_size); Ok((input, values)) } /// Helper to unpack a `Vec` into `Vec` where each byte represents 8 booleans. /// TODO: Use `BitSlice` from bitvec crate and avoid copying? pub fn decode_bytes_to_bools( bytes: &[u8], num_bools: usize, dec: &mut Decoder, ) -> MltResult> { if num_bools > bytes.len() * 8 { return Err(BufferUnderflow( u32::try_from(num_bools.div_ceil(8))?, bytes.len(), )); } let mut result = dec.alloc(num_bools)?; for i in 0..num_bools { result.push((bytes[i / 8] >> (i % 8)) & 1 == 1); } debug_assert_length(&result, num_bools); Ok(result) } #[inline] pub fn debug_assert_length(buffer: &[T], expected_len: usize) { debug_assert_eq!( buffer.len(), expected_len, "Expected buffer to have exact length" ); } #[cfg(test)] mod tests { use proptest::prelude::*; use super::*; use crate::test_helpers::{assert_empty, dec}; proptest! { #[test] fn encode_bools_to_bytes_roundtrip(bools: Vec) { let mut bytes = Vec::new(); let data = encode_bools_to_bytes(bools.iter().copied(), &mut bytes); let bools_rountrip = decode_bytes_to_bools(data, bools.len(), &mut dec()).unwrap(); prop_assert_eq!(bools_rountrip, bools); } #[test] fn test_u32_bytes_roundtrip(data: Vec) { let mut encoded = Vec::with_capacity(data.len() * 4); for val in &data { encoded.extend_from_slice(&val.to_le_bytes()); } let decoded = assert_empty(decode_bytes_to_u32s(&encoded, u32::try_from(data.len()).unwrap(), &mut dec())); prop_assert_eq!(data, decoded); } #[test] fn test_u64_bytes_roundtrip(data: Vec) { let mut encoded = Vec::with_capacity(data.len() * 8); for val in &data { encoded.extend_from_slice(&val.to_le_bytes()); } let decoded = assert_empty(decode_bytes_to_u64s(&encoded, u32::try_from(data.len()).unwrap(), &mut dec())); prop_assert_eq!(data, decoded); } } #[test] fn test_bytes_to_u32s_valid() { // Little-endian representation: // [0x04, 0x03, 0x02, 0x01] -> 0x01020304 // [0xDD, 0xCC, 0xBB, 0xAA] -> 0xAABBCCDD let bytes: [u8; 8] = [0x04, 0x03, 0x02, 0x01, 0xDD, 0xCC, 0xBB, 0xAA]; let u32s = assert_empty(decode_bytes_to_u32s(&bytes, 2, &mut dec())); assert_eq!( u32s, vec![0x0102_0304, 0xAABB_CCDD], "Decoded values should match" ); } #[test] fn test_bytes_to_u32s_empty() { let bytes: [u8; 0] = []; let u32s = assert_empty(decode_bytes_to_u32s(&bytes, 0, &mut dec())); assert!( u32s.is_empty(), "Output should be an empty Vec for 0 values" ); } #[test] fn test_bytes_to_u32s_buffer_underflow() { // Only 4 bytes but requesting 2 values (8 bytes needed) let bytes = [0x01, 0x02, 0x03, 0x04]; let res = decode_bytes_to_u32s(&bytes, 2, &mut dec()); assert!( res.is_err(), "Should error if not enough bytes for requested values" ); } #[test] fn test_bytes_to_u32s_partial_consumption() { // 12 bytes (3 values) but only requesting 2 values let bytes: [u8; 12] = [ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, ]; let res = decode_bytes_to_u32s(&bytes, 2, &mut dec()); assert!(res.is_ok(), "Should decode 2 values from larger buffer"); let (remaining, u32s) = res.unwrap(); assert_eq!(remaining.len(), 4, "Should have 4 bytes remaining"); assert_eq!(u32s.len(), 2); assert_eq!(u32s, vec![0x0403_0201, 0x0807_0605]); } #[test] fn test_decode_u32() { let bytes = [1, 0, 0, 0, 2, 0, 0, 0]; let expected = (&[][..], vec![1, 2]); let decoded = decode_bytes_to_u32s(&bytes, 2, &mut dec()).unwrap(); assert_eq!(decoded, expected); } #[test] fn test_decode_u64() { let bytes = [1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0]; let expected = (&[][..], vec![1, 2]); let decoded = decode_bytes_to_u64s(&bytes, 2, &mut dec()).unwrap(); assert_eq!(decoded, expected); } #[test] fn test_decode_bytes_to_u32s_empty() { let decoded = assert_empty(decode_bytes_to_u32s(&[], 0, &mut dec())); assert!(decoded.is_empty()); } } ================================================ FILE: rust/mlt-core/src/codecs/fastpfor.rs ================================================ use fastpfor::{AnyLenCodec as _, FastPFor256}; use crate::utils::AsUsize as _; use crate::{Decoder, MltError, MltResult}; /// Decode `FastPFOR`-compressed data using the composite codec protocol. /// /// The Java MLT encoder uses `Composition(FastPFOR(), VariableByte())`, matching /// the C++ `CompositeCodec, VariableByte>`. The wire format is: /// /// 1. First u32 = number of compressed u32 words from the primary codec (`FastPFor`) /// 2. Next N u32 words = primary codec (`FastPFor`) compressed data /// 3. Remaining u32 words = secondary codec (`VByte`) compressed data /// /// The compressed bytes are stored as big-endian u32 values by the Java encoder. pub fn decode_fastpfor(data: &[u8], num_values: u32, dec: &mut Decoder) -> MltResult> { if num_values == 0 { // FIXME: eventually there should not be a header anywhere at all return if data.is_empty() { Ok(vec![]) } else { Err(MltError::InvalidFastPforByteLength(0)) }; } // Convert big-endian bytes to u32 values if !data.len().is_multiple_of(4) { return Err(MltError::InvalidFastPforByteLength(data.len())); } // The Java MLT encoder writes compressed int[] → byte[] in big-endian order. // We must convert BE bytes → u32 to reconstruct the original integer values // that the Composition(FastPFOR, VariableByte) codec produced. let num_words = data.len() / 4; dec.consume_items::(num_words)?; let input: Vec = (0..num_words) .map(|i| { let o = i * 4; u32::from_be_bytes([data[o], data[o + 1], data[o + 2], data[o + 3]]) }) .collect(); let mut result = Vec::new(); FastPFor256::default().decode(&input, &mut result, Some(num_values))?; let Some(adjustment) = result .len() .checked_sub(num_values.as_usize()) .and_then(|v| u32::try_from(v).ok()) else { return Err(MltError::FastPforDecode(num_values, result.len())); }; dec.adjust(adjustment); result.truncate(num_values.as_usize()); Ok(result) } #[cfg(test)] mod tests { use proptest::prelude::*; use super::*; use crate::test_helpers::dec; proptest! { #[test] fn test_fastpfor_roundtrip(data: Vec) { // FastPFor256 produces a non-empty output (VByte header) even for empty input, // but decode_fastpfor requires zero bytes when num_values == 0 — consistent // with how PhysicalEncoder guards `if !values.is_empty()`. prop_assume!(!data.is_empty()); let mut encoded = Vec::new(); FastPFor256::default().encode(&data, &mut encoded).unwrap(); // Convert u32 words to big-endian bytes to match the wire format. let mut encoded2 = Vec::with_capacity(encoded.len() * 4); for word in &encoded { encoded2.extend_from_slice(&word.to_be_bytes()); } let decoded = decode_fastpfor(&encoded2, data.len().try_into().unwrap(), &mut dec()).unwrap(); prop_assert_eq!(data, decoded); } } #[test] fn test_decode_fastpfor_empty() { let decoded = decode_fastpfor(&[], 0, &mut dec()).unwrap(); assert!(decoded.is_empty()); } } ================================================ FILE: rust/mlt-core/src/codecs/fsst.rs ================================================ use crate::decoder::RawFsstData; use crate::utils::AsUsize as _; use crate::{Decoder, MltResult}; /// Decode an FSST-compressed byte sequence into the original bytes and value lengths, /// charging `dec` for the output. /// /// Takes a `RawFsstData` which provides the 4 streams needed for FSST decoding: /// - `symbol_lengths`: per-symbol byte lengths (decoded as u32 values) /// - `symbol_table`: concatenated raw symbol bytes (read as raw bytes) /// - `lengths`: original string byte lengths (decoded as u32 values) /// - `corpus`: the FSST-encoded payload (read as raw bytes) /// /// The encoding uses two special cases: /// - Byte `0xFF` (255): the next byte is a literal — output it verbatim. /// - Any other byte `idx < symbol_lengths.len()`: expand the symbol at that index. /// /// Returns `(decompressed_utf8_string, value_lengths)`. pub fn decode_fsst(raw: RawFsstData<'_>, dec: &mut Decoder) -> MltResult<(String, Vec)> { let RawFsstData { symbol_lengths, symbol_table, lengths, corpus, } = raw; let sym_lens = symbol_lengths.decode_u32s(dec)?; let symbols = symbol_table.data; let compressed = corpus.data; // Build symbol offset table from lengths. let mut symbol_offsets = vec![0u32; sym_lens.len()]; for i in 1..sym_lens.len() { symbol_offsets[i] = symbol_offsets[i - 1] + sym_lens[i - 1]; } let mut output = Vec::new(); let mut i = 0; while i < compressed.len() { let sym_idx = usize::from(compressed[i]); if sym_idx == 255 { i += 1; output.push(compressed[i]); } else if sym_idx < sym_lens.len() { let len = sym_lens[sym_idx].as_usize(); let off = symbol_offsets[sym_idx].as_usize(); output.extend_from_slice(&symbols[off..off + len]); } i += 1; } dec.consume_items::(output.len())?; Ok((String::from_utf8(output)?, lengths.decode_u32s(dec)?)) } /// Raw output from FSST compression (unencoded byte buffers). /// /// Pass to the string encoder's `write_fsst_data` helper to write these /// streams directly to an [`Encoder`](crate::encoder::Encoder). pub struct FsstRawData { /// Per-symbol byte lengths (to be written as `Length(Symbol)` stream). pub symbol_lengths: Vec, /// Concatenated raw symbol bytes (to be written as `Data(Fsst)` stream). pub symbol_bytes: Vec, /// Per-value byte lengths of the compressed corpus (to be written as `Length(Dictionary)` stream). pub value_lengths: Vec, /// FSST-compressed corpus bytes (to be written as `Data(dict_type)` stream). pub corpus: Vec, } /// Shared FSST compression kernel: train a compressor on `values` and compress the corpus. /// /// Returns [`FsstRawData`] with the four raw byte/int buffers ready to be written to /// an encoder via the caller's chosen integer encoders. /// /// Stream order when written: /// 1. Symbol lengths (`Length(Symbol)`) /// 2. Symbol table data (`Data(Fsst)`) /// 3. Value lengths (`Length(Dictionary)`) /// 4. Compressed corpus (`Data(dict_type)` — supplied by the caller at write time) /// /// Note: The FSST algorithm implementation may differ from Java's, so the /// compressed output may not be byte-for-byte identical. Both implementations /// are semantically compatible and can decode each other's output. pub fn compress_fsst>(values: &[S]) -> FsstRawData { let byte_slices: Vec<&[u8]> = values.iter().map(|s| s.as_ref().as_bytes()).collect(); let compressor = fsst::Compressor::train(&byte_slices); compress_fsst_with(values, &compressor) } /// Like [`compress_fsst`] but reuses an already-trained [`fsst::Compressor`]. pub fn compress_fsst_with>( values: &[S], compressor: &fsst::Compressor, ) -> FsstRawData { let symbols = compressor.symbol_table(); let symbol_lengths_u8 = compressor.symbol_lengths(); let mut symbol_bytes = Vec::new(); for sym in symbols { let bytes = sym.to_u64().to_le_bytes(); let len = sym.len(); symbol_bytes.extend_from_slice(&bytes[..len]); } let symbol_lengths: Vec = symbol_lengths_u8 .iter() .take(symbols.len()) .map(|&l| u32::from(l)) .collect(); let value_lengths: Vec = values .iter() .map(|s| u32::try_from(s.as_ref().len()).unwrap_or(u32::MAX)) .collect(); // Compress all strings as one concatenated buffer. // This allows FSST symbol matches across string boundaries. // For example: `"sdfAAAA" + "AAAAyxc"` may now compress more `A`s. // The decoder decompresses the full corpus and splits by original (uncompressed) value lengths. let concatenated: Vec = values .iter() .flat_map(|s| s.as_ref().as_bytes()) .copied() .collect(); let corpus = compressor.compress(&concatenated); FsstRawData { symbol_lengths, symbol_bytes, value_lengths, corpus, } } #[cfg(test)] mod tests { use rstest::rstest; use super::*; use crate::decoder::{DictionaryType, LengthType, RawFsstData, RawStream, StreamType}; use crate::encoder::model::StreamCtx; use crate::encoder::{Codecs, EncodedStream, Encoder, ExplicitEncoder, IntEncoder}; use crate::test_helpers::{assert_empty, dec, parser}; use crate::utils::BinarySerializer as _; /// Encode the 4 FSST raw streams to wire bytes and parse them back for decoding. fn roundtrip(values: &[&str]) -> (String, Vec) { use crate::decoder::StreamMeta; let raw = compress_fsst(values); let sym_len_bytes = { let mut enc = Encoder::with_explicit( Encoder::default().cfg, ExplicitEncoder::all(IntEncoder::varint()), ); let mut codecs = Codecs::default(); let ctx = StreamCtx::prop(StreamType::Length(LengthType::Symbol), "symbol"); codecs .write_int_stream(&raw.symbol_lengths, &ctx, &mut enc) .unwrap(); enc.data }; let sym_table_stream = EncodedStream { meta: StreamMeta::new_none( StreamType::Data(DictionaryType::Fsst), raw.symbol_lengths.len(), ) .unwrap(), data: raw.symbol_bytes.clone(), }; let lengths_bytes = { let mut enc = Encoder::with_explicit( Encoder::default().cfg, ExplicitEncoder::all(IntEncoder::varint()), ); let mut codecs = Codecs::default(); let ctx = StreamCtx::prop(StreamType::Length(LengthType::Dictionary), "dictionary"); codecs .write_int_stream(&raw.value_lengths, &ctx, &mut enc) .unwrap(); enc.data }; let corpus_stream = EncodedStream { meta: StreamMeta::new_none(StreamType::Data(DictionaryType::Single), values.len()) .unwrap(), data: raw.corpus.clone(), }; let mut sym_table_buf = Vec::new(); sym_table_buf .write_stream(&sym_table_stream) .expect("write_stream failed"); let mut corpus_buf = Vec::new(); corpus_buf .write_stream(&corpus_stream) .expect("write_stream failed"); let buffers = [sym_len_bytes, sym_table_buf, lengths_bytes, corpus_buf]; let mut raw_streams = Vec::new(); for buf in &buffers { raw_streams.push(assert_empty(RawStream::from_bytes(buf, &mut parser()))); } let [s0, s1, s2, s3] = raw_streams.try_into().expect("expected 4 streams"); let raw = RawFsstData::new(s0, s1, s2, s3).expect("RawFsstData::new failed"); decode_fsst(raw, &mut dec()).expect("decode_fsst failed") } #[test] fn test_fsst_roundtrip_empty() { let (corpus, lengths) = roundtrip(&[]); assert!(corpus.is_empty()); assert!(lengths.is_empty()); } #[rstest] #[case::longer(&["hello world", "hello rust", "hello fsst", "world"])] #[case::short(&["hello"])] fn automatic_optimization_roundtrip(#[case] values: &[&str]) { let (corpus, lengths) = roundtrip(values); let mut offset = 0; for (s, &len) in values.iter().zip(&lengths) { let len = len as usize; assert_eq!(&corpus[offset..offset + len], *s); offset += len; } } } ================================================ FILE: rust/mlt-core/src/codecs/hilbert.rs ================================================ use geo_types::Coord; use crate::encoder::model::CurveParams; /// Return the 1-D Hilbert curve index for `(x, y)` at the given `level`. /// /// The grid has side `2^level`; both `x` and `y` must be in `[0, 2^level)`, /// and `level` must be in `[1, 16]`. The returned index is in /// `[0, 4^level)` and fits in a `u32` for all valid levels. #[must_use] pub fn hilbert_xy_to_index(level: u32, coord: Coord) -> u32 { debug_assert!((1..=16).contains(&level), "level must be in [1, 16]"); debug_assert!(coord.x < (1 << level), "x out of range for level"); debug_assert!(coord.y < (1 << level), "y out of range for level"); hilbert_2d::u32::xy2h_discrete(coord.x, coord.y, level, hilbert_2d::Variant::Hilbert) } /// Compute a Hilbert curve sort key from signed integer coordinates. /// /// `shift` is added to both axes to move the origin into the non-negative /// range (use the global minimum across *all* vertices for the layer, not /// per-axis). `bits` is the grid level — both shifted components must /// fit in `[0, 2^bits)`. /// /// Use [`hilbert_curve_params_from_bounds`] to compute `shift` and `bits` /// from global min/max coordinates. #[must_use] pub fn hilbert_sort_key(c: Coord, params: CurveParams) -> u32 { debug_assert!((1..=16).contains(¶ms.bits)); #[expect( clippy::cast_possible_truncation, clippy::cast_sign_loss, reason = "shift brings value into [0, extent]; masked to 16 bits immediately after" )] let sx = ((i64::from(c.x) + i64::from(params.shift)) as u32) & 0xFFFF; #[expect( clippy::cast_possible_truncation, clippy::cast_sign_loss, reason = "shift brings value into [0, extent]; masked to 16 bits immediately after" )] let sy = ((i64::from(c.y) + i64::from(params.shift)) as u32) & 0xFFFF; hilbert_xy_to_index(params.bits, (sx, sy).into()) } /// Compute the coordinate shift and grid level from pre-computed global /// min/max values across all vertex coordinates (both axes combined). /// /// Returns `(shift, bits)` where: /// - `shift` is subtracted from the global minimum (i.e. it equals /// `min_val.unsigned_abs()` when `min_val < 0`, else `0`), ensuring all /// shifted coordinates are non-negative. /// - `bits` is the smallest level `l` in `[1, 16]` such that all /// shifted values fit in `[0, 2^l)`. /// /// If `min > max` (empty input), returns `(0, 1)`. #[must_use] pub fn hilbert_curve_params_from_bounds(min_val: i32, max_val: i32) -> CurveParams { if min_val > max_val { return CurveParams { shift: 0, bits: 1 }; } let shift: u32 = if min_val < 0 { min_val.unsigned_abs() } else { 0 }; // extent = largest shifted coordinate value that any vertex can take. let extent = (i64::from(max_val) + i64::from(shift)).unsigned_abs(); // bits = ceil(log2(extent + 1)), i.e. the smallest l s.t. 2^l > extent. // For extent = 0 the grid degenerates to a single cell; use level 1. let bits = if extent == 0 { 1 } else { (u64::BITS - extent.leading_zeros()).min(16) }; CurveParams { shift, bits } } // ─── Tests ─────────────────────────────────────────────────────────────────── #[cfg(test)] mod tests { use super::*; use crate::codecs::morton::interleave_bits; const fn c(x: i32, y: i32) -> Coord { Coord:: { x, y } } const fn p(shift: u32, bits: u32) -> CurveParams { CurveParams { shift, bits } } /// Return the `(x, y)` coordinates for Hilbert curve index `pos` at `level`. /// /// This is the inverse of [`hilbert_xy_to_index`]: the returned coordinates /// are in `[0, 2^level)`. `level` must be in `[1, 16]` and `pos` must be /// in `[0, 4^level)`. fn hilbert_position_to_xy(level: u32, pos: u32) -> Coord { debug_assert!((1..=16).contains(&level), "level must be in [1, 16]"); debug_assert!(u64::from(pos) < (1u64 << (2 * level)), "pos out of range"); hilbert_2d::u32::h2xy_discrete(pos, level, hilbert_2d::Variant::Hilbert).into() } #[test] fn hilbert_origin_always_zero() { // The origin maps to index 0 at every level. for level in 1u32..=8 { let idx = hilbert_xy_to_index(level, (0, 0).into()); assert_eq!(idx, 0, "origin should be 0 at level {level}"); } } #[test] fn hilbert_round_trip_level1() { // 2×2 grid: all four cells must encode then decode back to themselves. for x in 0u32..2 { for y in 0u32..2 { let idx = hilbert_xy_to_index(1, (x, y).into()); let (rx, ry) = hilbert_position_to_xy(1, idx).into(); assert_eq!((rx, ry), (x, y), "round-trip failed at level=1 ({x},{y})"); } } } #[test] fn hilbert_round_trip_level2() { // 4×4 grid: all 16 cells. for x in 0u32..4 { for y in 0u32..4 { let idx = hilbert_xy_to_index(2, (x, y).into()); let (rx, ry) = hilbert_position_to_xy(2, idx).into(); assert_eq!((rx, ry), (x, y), "round-trip failed at level=2 ({x},{y})"); } } } #[test] fn hilbert_round_trip_level4() { // 16×16 grid: all 256 cells. for x in 0u32..16 { for y in 0u32..16 { let idx = hilbert_xy_to_index(4, (x, y).into()); let (rx, ry) = hilbert_position_to_xy(4, idx).into(); assert_eq!((rx, ry), (x, y), "round-trip failed at level=4 ({x},{y})"); } } } #[test] fn hilbert_indices_are_a_bijection_at_level2() { // Every index in [0, 16) must appear exactly once. let mut seen = [false; 16]; for x in 0u32..4 { for y in 0u32..4 { let idx = hilbert_xy_to_index(2, (x, y).into()) as usize; assert!(!seen[idx], "duplicate index {idx} at ({x},{y})"); seen[idx] = true; } } assert!(seen.iter().all(|&v| v), "some index was never produced"); } #[test] fn hilbert_indices_are_a_bijection_at_level4() { let mut seen = vec![false; 256]; for x in 0u32..16 { for y in 0u32..16 { let idx = hilbert_xy_to_index(4, (x, y).into()) as usize; assert!(!seen[idx], "duplicate index {idx} at ({x},{y})"); seen[idx] = true; } } assert!(seen.iter().all(|&v| v)); } #[test] fn hilbert_level1_covers_indices_0_to_3() { // Collect all indices produced for the 2×2 grid. let mut indices: Vec = (0u32..2) .flat_map(|x| (0u32..2).map(move |y| hilbert_xy_to_index(1, (x, y).into()))) .collect(); indices.sort_unstable(); assert_eq!(indices, [0, 1, 2, 3]); } // ── hilbert_sort_key ────────────────────────────────────────────────────── #[test] fn hilbert_sort_key_origin_zero() { assert_eq!(hilbert_sort_key(c(0, 0), p(0, 1)), 0); } #[test] fn hilbert_sort_key_negative_coords_shift_correctly() { // (-1, -1) shifted by 1 maps to (0, 0) -> Hilbert index 0 at any level. assert_eq!(hilbert_sort_key(c(-1, -1), p(1, 1)), 0); } #[test] fn hilbert_sort_key_matches_xy_to_index() { // hilbert_sort_key should agree with a direct call to hilbert_xy_to_index // after shifting. let shift = 5u32; let bits = 4u32; for raw_x in -5i32..11 { for raw_y in -5i32..11 { let expected = hilbert_xy_to_index( bits, ( u32::try_from(i64::from(raw_x) + i64::from(shift)).unwrap(), u32::try_from(i64::from(raw_y) + i64::from(shift)).unwrap(), ) .into(), ); let actual = hilbert_sort_key(c(raw_x, raw_y), p(shift, bits)); assert_eq!(actual, expected, "mismatch at ({raw_x},{raw_y})"); } } } #[test] fn curve_params_empty_bounds() { // min > max signals empty input. let CurveParams { shift, bits } = hilbert_curve_params_from_bounds(i32::MAX, i32::MIN); assert_eq!(shift, 0); assert_eq!(bits, 1); } #[test] fn curve_params_all_zero() { // Degenerate: single point at origin, extent = 0 -> level 1. let CurveParams { shift, bits } = hilbert_curve_params_from_bounds(0, 0); assert_eq!(shift, 0); assert_eq!(bits, 1); } #[test] fn curve_params_positive_only() { // Bounds [0, 3]: shift = 0, bits = 2 (2^2=4 > 3). let CurveParams { shift, bits } = hilbert_curve_params_from_bounds(0, 3); assert_eq!(shift, 0); assert_eq!(bits, 2); } #[test] fn curve_params_negative_min() { // Bounds [-4, 4]: shift = 4, extent = 4+4 = 8, bits = 4 (2^4=16 > 8). let CurveParams { shift, bits } = hilbert_curve_params_from_bounds(-4, 4); assert_eq!(shift, 4); assert_eq!(bits, 4); } #[test] fn curve_params_power_of_two_extent() { // extent = 8 = 2^3: need level 4 so that 2^4 = 16 > 8. let CurveParams { shift, bits } = hilbert_curve_params_from_bounds(0, 8); assert_eq!(shift, 0); assert_eq!(bits, 4); } #[test] fn curve_params_single_axis_negative() { // Global min = -2 -> shift = 2; extent = 5 + 2 = 7; bits = 3. let CurveParams { shift, bits } = hilbert_curve_params_from_bounds(-2, 5); assert_eq!(shift, 2); assert_eq!(bits, 3); } #[test] fn curve_params_clamped_at_16_bits() { // extent = 65535 = 2^16 - 1, bits = 16. let CurveParams { shift, bits } = hilbert_curve_params_from_bounds(0, 65535); assert_eq!(shift, 0); assert_eq!(bits, 16); } // ── Hilbert vs Morton locality comparison ───────────────────────────────── #[test] fn hilbert_and_morton_both_sort_nearby_points_close_together() { // A dense cluster of points in [0, 7]×[0, 7] should produce // keys that are all close to each other under both curves. // We verify that the maximum key spread for a 2×2 neighbourhood is // smaller than the total key range for the full 8×8 grid. let points: Vec> = (0u32..8) .flat_map(|x| (0u32..8).map(move |y| (x, y).into())) .collect(); // Hilbert: level = 3 (2^3 = 8) let h_keys: Vec = points.iter().map(|&c| hilbert_xy_to_index(3, c)).collect(); let h_max = h_keys.iter().copied().max().unwrap(); // Morton let m_keys: Vec = points.iter().map(|&c| interleave_bits(c)).collect(); let m_max = m_keys.iter().copied().max().unwrap(); // Both should cover the full range for the grid. assert_eq!(h_max, 63, "Hilbert should produce indices 0..63 for 8×8"); assert_eq!(m_max, interleave_bits((7, 7).into())); // Neither curve should map (0,0) and (1,0) more than 4 steps apart at level 3. let h_00 = hilbert_xy_to_index(3, (0, 0).into()); let h_10 = hilbert_xy_to_index(3, (1, 0).into()); assert!( h_00.abs_diff(h_10) <= 4, "adjacent points should have close Hilbert indices" ); } } ================================================ FILE: rust/mlt-core/src/codecs/mod.rs ================================================ pub mod bytes; pub mod fastpfor; pub mod fsst; pub mod hilbert; pub mod morton; pub mod rle; pub mod varint; pub mod zigzag; ================================================ FILE: rust/mlt-core/src/codecs/morton.rs ================================================ use geo_types::Coord; use wide::u32x8; use crate::decoder::Morton; use crate::encoder::model::CurveParams; use crate::{Decoder, MltError, MltResult}; const LANES: usize = 8; // ── Bit interleaving ───────────────────────────────────────────────────────── /// Interleave the lower 16 bits of `x` and `y` into a 32-bit Morton code. /// /// Even bit positions (0, 2, 4, …) encode `x`; odd positions (1, 3, 5, …) /// encode `y`. Spatially adjacent `(x, y)` pairs produce numerically /// adjacent codes, giving Z-order locality when used as a sort key. #[must_use] #[inline] pub fn interleave_bits(coord: Coord) -> u32 { // Spread each input's lower 16 bits into every other bit position, then // OR the two together: x occupies even positions (0, 2, 4, …) and y // occupies odd positions (1, 3, 5, …). let mut sx = coord.x & 0xFFFF; sx = (sx | (sx << 8)) & 0x00FF_00FF; sx = (sx | (sx << 4)) & 0x0F0F_0F0F; sx = (sx | (sx << 2)) & 0x3333_3333; sx = (sx | (sx << 1)) & 0x5555_5555; let mut sy = coord.y & 0xFFFF; sy = (sy | (sy << 8)) & 0x00FF_00FF; sy = (sy | (sy << 4)) & 0x0F0F_0F0F; sy = (sy | (sy << 2)) & 0x3333_3333; sy = (sy | (sy << 1)) & 0x5555_5555; sx | (sy << 1) } /// Compute a Z-order (Morton) sort key from signed integer coordinates. /// /// `shift` is applied to both axes before bit-interleaving to move the /// coordinate origin into the non-negative range. It should be computed /// once across the entire feature set (typically `min.unsigned_abs()` when /// `min < 0`, else `0`) so that the keys are comparable across features. /// /// Each shifted component is truncated to 16 bits before interleaving, so /// the returned key fits in a `u32` (32 interleaved bits). This is /// sufficient for any tile coordinate system with extent ≤ 65 535. #[must_use] #[inline] pub fn morton_sort_key(c: Coord, params: CurveParams) -> u32 { debug_assert!((1..=16).contains(¶ms.bits)); #[expect( clippy::cast_possible_truncation, clippy::cast_sign_loss, reason = "shift brings value into [0, extent]; masked to 16 bits immediately after" )] let sx = ((i64::from(c.x) + i64::from(params.shift)) as u32) & 0xFFFF; #[expect( clippy::cast_possible_truncation, clippy::cast_sign_loss, reason = "shift brings value into [0, extent]; masked to 16 bits immediately after" )] let sy = ((i64::from(c.y) + i64::from(params.shift)) as u32) & 0xFFFF; interleave_bits((sx, sy).into()) } // ── Encoder ───────────────────────────────────────────────────────────────── impl Morton { /// Compute `ZOrderCurve` parameters from the vertex value range. /// /// Returns a [`Morton`] whose `bits` and `shift` match Java's /// `SpaceFillingCurve` implementation. pub fn from_vertices(vertices: &[i32]) -> MltResult { let min_v = vertices.iter().copied().min().unwrap_or(0); let max_v = vertices.iter().copied().max().unwrap_or(0); let shift: u32 = if min_v < 0 { min_v.unsigned_abs() } else { 0 }; let tile_extent = i64::from(max_v) + i64::from(shift); let bits = if let Ok(extent) = u32::try_from(tile_extent) { // ceil(log2(extent + 1)), matching Java's Math.ceil(Math.log(...) / Math.log(2)). // Computed with integer arithmetic: for te >= 1, this equals `u32::BITS - te.leading_zeros()`. // Capped at 16: Morton codes are u32, so each axis may use at most 16 bits. let required_bits = u32::BITS - extent.leading_zeros(); if required_bits > 16 { return Err(MltError::VertexMortonNotCompatibleWithExtent { extent, required_bits, }); } required_bits } else { 0u32 }; Self::new(bits, shift) } /// Encode a single `(x, y)` coordinate pair to its Z-order (Morton) code. /// /// `bits` (≤ 16) bits are used per axis; `shift` is added to each /// component before interleaving so that negative coordinates map to non-negative values. #[inline] pub fn encode_morton(self, x: i32, y: i32) -> MltResult { let sx = u32::try_from(i64::from(x) + i64::from(self.shift))?; let sy = u32::try_from(i64::from(y) + i64::from(self.shift))?; let mut code = 0u32; for i in 0..self.bits { // bits are capped at 16, so 2*i+1 ≤ 31 — no shift overflow. code |= ((sx >> i) & 1) << (2 * i); code |= ((sy >> i) & 1) << (2 * i + 1); } Ok(code) } } impl Morton { /// Decode a single Morton code to a `Coord`, applying `shift`. #[inline] fn decode_one(self, morton_code: u32) -> Coord { let mut x = 0u32; let mut y = 0u32; for i in 0..self.bits { let bit_mask = 1u32 << (2 * i); x |= (morton_code & bit_mask) >> i; y |= ((morton_code >> 1) & bit_mask) >> i; } Coord:: { x: x.wrapping_sub(self.shift).cast_signed(), y: y.wrapping_sub(self.shift).cast_signed(), } } /// Decode Morton codes (no delta) to flat `[x0, y0, x1, y1, ...]`, charging `dec` for the output. /// /// Processes 8 codes at a time with `wide::u32x8`. Each lane extracts the /// compacted even-bit (x) and odd-bit (y) components in parallel, then applies /// the coordinate shift. A scalar tail handles any remaining codes. pub fn decode_codes(self, data: &[u32], dec: &mut Decoder) -> MltResult> { let alloc_size = data.len() * 2; let mut out = dec.alloc(alloc_size)?; let shift_vec = u32x8::splat(self.shift); let mut chunks = data.chunks_exact(LANES); for chunk in chunks.by_ref() { let buf = [ chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7], ]; self.decode_chunk(buf, shift_vec, &mut out); } // Scalar tail for any codes that didn't fill a full SIMD chunk. for &code in chunks.remainder() { let coord = self.decode_one(code); out.push(coord.x); out.push(coord.y); } dec.adjust_alloc(&out, alloc_size)?; Ok(out) } /// Decode delta-encoded Morton codes to flat `[x0, y0, x1, y1, ...]`, charging `dec` for the output. /// /// Each input value is a signed delta (stored as u32 with wrapping arithmetic) /// relative to the previous Morton code. The sequential prefix sum is computed /// in chunks of 8 into a stack-allocated buffer, which is then SIMD-decoded. /// This keeps the working set in registers / L1 cache. pub fn decode_delta(self, data: &[u32], dec: &mut Decoder) -> MltResult> { let alloc_size = data.len() * 2; let mut out = dec.alloc(alloc_size)?; let shift_vec = u32x8::splat(self.shift); let mut prev = 0i32; let mut chunks = data.chunks_exact(LANES); for chunk in chunks.by_ref() { // Sequential prefix sum into a stack buffer — no heap allocation. let mut buf = [0u32; LANES]; for (b, &d) in buf.iter_mut().zip(chunk.iter()) { prev = prev.wrapping_add(d.cast_signed()); *b = prev.cast_unsigned(); } self.decode_chunk(buf, shift_vec, &mut out); } // Scalar tail for any codes that didn't fill a full SIMD chunk. for &d in chunks.remainder() { prev = prev.wrapping_add(d.cast_signed()); let coord = self.decode_one(prev.cast_unsigned()); out.push(coord.x); out.push(coord.y); } dec.adjust_alloc(&out, alloc_size)?; Ok(out) } /// SIMD-decode a chunk of exactly 8 resolved Morton codes into the output buffer. /// /// Each code has already been resolved to its absolute value (no delta pending). /// Even-indexed bits encode x, odd-indexed bits encode y. #[inline] fn decode_chunk(self, buf: [u32; LANES], shift_vec: u32x8, out: &mut Vec) { let codes = u32x8::from(buf); // Odd bits become even after shifting right by 1, giving the y component. let codes_y = codes >> 1; let mut x_vec = u32x8::ZERO; let mut y_vec = u32x8::ZERO; for i in 0..self.bits { // Mask for the bit position 2*i in the original Morton code. let bit_mask = u32x8::splat(1u32 << (2 * i)); // Extract bit 2*i from each code and shift it down to position i. x_vec |= (codes & bit_mask) >> i; y_vec |= (codes_y & bit_mask) >> i; } let xs: [u32; LANES] = (x_vec - shift_vec).into(); let ys: [u32; LANES] = (y_vec - shift_vec).into(); for lane in 0..LANES { out.push(xs[lane].cast_signed()); out.push(ys[lane].cast_signed()); } } } #[cfg(test)] mod tests { use super::*; use crate::test_helpers::dec; const fn c(x: i32, y: i32) -> Coord { Coord:: { x, y } } const fn p(shift: u32, bits: u32) -> CurveParams { CurveParams { shift, bits } } // ── interleave_bits / morton_sort_key ───────────────────────────────────── /// Spread the lower 16 bits of `tx` into the even bit positions (0, 2, 4, …) /// of a 32-bit word, inserting a 0 between every original bit. fn spread_bits(mut tx: u32) -> u32 { tx = (tx | (tx << 8)) & 0x00FF_00FF; tx = (tx | (tx << 4)) & 0x0F0F_0F0F; tx = (tx | (tx << 2)) & 0x3333_3333; tx = (tx | (tx << 1)) & 0x5555_5555; tx } /// Compact the bits at even positions (0, 2, 4, …) of `tx` into the lower /// 16 bits, discarding the interleaved zeros. fn compact_bits(mut tx: u32) -> u32 { tx &= 0x5555_5555; tx = (tx | (tx >> 1)) & 0x3333_3333; tx = (tx | (tx >> 2)) & 0x0F0F_0F0F; tx = (tx | (tx >> 4)) & 0x00FF_00FF; tx = (tx | (tx >> 8)) & 0x0000_FFFF; tx } #[test] fn spread_then_compact_is_identity() { for x in 0u32..=0xFFFF { assert_eq!(compact_bits(spread_bits(x)), x, "round-trip failed for {x}"); } } #[test] fn spread_bits_places_bit0_at_position0() { assert_eq!(spread_bits(1), 1); } #[test] fn spread_bits_places_bit1_at_position2() { assert_eq!(spread_bits(2), 4); } #[test] fn spread_bits_places_bit2_at_position4() { assert_eq!(spread_bits(4), 16); } #[test] fn origin_maps_to_zero() { assert_eq!(morton_sort_key(c(0, 0), p(0, 16)), 0); } #[test] fn x_axis_produces_even_bits() { // x=1, y=0 → only bit 0 of x is set → Morton bit 0 set → code = 1 assert_eq!(morton_sort_key(c(1, 0), p(0, 16)), 1); // x=2, y=0 → only bit 1 of x is set → Morton bit 2 set → code = 4 assert_eq!(morton_sort_key(c(2, 0), p(0, 16)), 4); } #[test] fn y_axis_produces_odd_bits() { // x=0, y=1 → only bit 0 of y is set → Morton bit 1 set → code = 2 assert_eq!(morton_sort_key(c(0, 1), p(0, 16)), 2); // x=0, y=2 → only bit 1 of y is set → Morton bit 3 set → code = 8 assert_eq!(morton_sort_key(c(0, 2), p(0, 16)), 8); } #[test] fn negative_coords_shift_correctly() { // Shifting (-1, -1) by 1 maps to (0, 0) → Morton code 0 assert_eq!(morton_sort_key(c(-1, -1), p(1, 16)), 0); // Shifting (-1, 0) by 1 maps to (0, 1) → Morton code 2 assert_eq!(morton_sort_key(c(-1, 0), p(1, 16)), 2); } #[test] fn spatial_locality_z_order() { // After shifting, (0,0) < (1,0) < (0,1) < (1,1) in Z-order let k00 = morton_sort_key(c(0, 0), p(0, 16)); let k10 = morton_sort_key(c(1, 0), p(0, 16)); let k01 = morton_sort_key(c(0, 1), p(0, 16)); let k11 = morton_sort_key(c(1, 1), p(0, 16)); assert!(k00 < k10); assert!(k10 < k01); assert!(k01 < k11); } #[test] fn interleave_round_trips_via_deinterleave() { // Reconstruct x and y from interleaved bits and verify round-trip. for x in 0u32..16 { for y in 0u32..16 { let code = interleave_bits((x, y).into()); let mut rx = 0u32; let mut ry = 0u32; for bit in 0..16 { rx |= ((code >> (2 * bit)) & 1) << bit; ry |= ((code >> (2 * bit + 1)) & 1) << bit; } assert_eq!(rx, x, "x mismatch for ({x}, {y})"); assert_eq!(ry, y, "y mismatch for ({x}, {y})"); } } } // ── Morton encode/decode tests ──────────────────────────────────────────── const NUM_BITS: u32 = 15; const COORD_SHIFT: u32 = 1 << (NUM_BITS - 1); // 16384 const MORTON: Morton = Morton { bits: NUM_BITS, shift: COORD_SHIFT, }; /// Interleave `x` and `y` into a single Morton code using 15 bits per component. /// /// Even bit positions encode `x`, odd positions encode `y`. /// This is the inverse of [`Morton::decode_codes`] / [`Morton::decode_delta`]. #[must_use] #[inline] pub fn encode_morton_15(coord: Coord) -> u32 { let mut code = 0u32; for bit in 0..15 { code |= ((coord.x >> bit) & 1) << (2 * bit); code |= ((coord.y >> bit) & 1) << (2 * bit + 1); } code } #[test] fn test_decode_morton_codes_empty() { assert!(MORTON.decode_codes(&[], &mut dec()).unwrap().is_empty()); } #[test] fn test_decode_morton_codes_origin() { // Morton code for (COORD_SHIFT, COORD_SHIFT) should decode to (0, 0). let code = encode_morton_15((COORD_SHIFT, COORD_SHIFT).into()); let decoded = MORTON.decode_codes(&[code], &mut dec()).unwrap(); assert_eq!(decoded, [0, 0]); } #[test] fn test_decode_morton_codes_known_values() { // x=1, y=2 (pre-shift) → decoded (1 - COORD_SHIFT, 2 - COORD_SHIFT) let x: u32 = 1; let y: u32 = 2; let code = encode_morton_15((x, y).into()); let expected_x = x.cast_signed() - COORD_SHIFT.cast_signed(); let expected_y = y.cast_signed() - COORD_SHIFT.cast_signed(); let decoded = MORTON.decode_codes(&[code], &mut dec()).unwrap(); assert_eq!(decoded, [expected_x, expected_y]); } #[test] fn test_decode_morton_codes_scalar_tail() { // 3 codes — exercises the scalar tail path (< 8 codes). let pairs: [Coord; _] = [(0, 1).into(), (2, 3).into(), (4, 5).into()]; let codes: Vec = pairs.iter().map(|&c| encode_morton_15(c)).collect(); let result = MORTON.decode_codes(&codes, &mut dec()).unwrap(); let expected = expected_coords(&pairs); assert_eq!(result, expected); } #[test] fn test_decode_morton_codes_full_simd_chunk() { // 8 codes — exercises exactly one SIMD chunk, no scalar tail. let pairs: [Coord; _] = [ (0, 0).into(), (1, 0).into(), (0, 1).into(), (1, 1).into(), (2, 3).into(), (7, 5).into(), (10, 9).into(), (15, 15).into(), ]; let codes: Vec = pairs.iter().map(|&c| encode_morton_15(c)).collect(); let result = MORTON.decode_codes(&codes, &mut dec()).unwrap(); let expected = expected_coords(&pairs); assert_eq!(result, expected); } #[test] fn test_decode_morton_codes_simd_plus_tail() { // 11 codes — one full SIMD chunk of 8 plus a scalar tail of 3. let pairs: Vec> = (0..11u32) .map(|i| (i * 3 % 100, i * 7 % 100).into()) .collect(); let codes: Vec = pairs.iter().map(|&c| encode_morton_15(c)).collect(); let result = MORTON.decode_codes(&codes, &mut dec()).unwrap(); let expected = expected_coords(&pairs); assert_eq!(result, expected); } // --- decode_delta tests --- #[test] fn test_decode_morton_delta_empty() { assert!(MORTON.decode_delta(&[], &mut dec()).unwrap().is_empty()); } #[test] fn test_decode_morton_delta_identity_with_zero_deltas() { // All-zero deltas: every resolved code is 0, which decodes to (-COORD_SHIFT, -COORD_SHIFT). let deltas = vec![0u32; 3]; let result = MORTON.decode_delta(&deltas, &mut dec()).unwrap(); let shift = -COORD_SHIFT.cast_signed(); assert_eq!(result, vec![shift, shift, shift, shift, shift, shift]); } #[test] fn test_decode_morton_delta_matches_codes_after_prefix_sum() { // Build a sequence of absolute codes, compute their deltas, then verify that // decode_delta produces the same output as decode_codes on the original absolute codes. let pairs: Vec> = (0..11u32) .map(|i| (i * 5 % 200, i * 9 % 200).into()) .collect(); let codes: Vec = pairs.iter().map(|&c| encode_morton_15(c)).collect(); let deltas = signed_deltas(&codes); let from_codes = MORTON.decode_codes(&codes, &mut dec()).unwrap(); let from_deltas = MORTON.decode_delta(&deltas, &mut dec()).unwrap(); assert_eq!(from_codes, from_deltas); } #[test] fn test_decode_morton_delta_scalar_tail() { // 3 codes via deltas — scalar tail path only. let codes: Vec = vec![ encode_morton_15((10, 20).into()), encode_morton_15((30, 40).into()), encode_morton_15((50, 60).into()), ]; let deltas = signed_deltas(&codes); let from_codes = MORTON.decode_codes(&codes, &mut dec()).unwrap(); let from_deltas = MORTON.decode_delta(&deltas, &mut dec()).unwrap(); assert_eq!(from_codes, from_deltas); } #[test] fn test_decode_morton_delta_wrapping() { // A single wrapping delta: start from a large code, subtract more than it — should // still round-trip correctly via wrapping arithmetic. let code_a = encode_morton_15((500, 300).into()); let code_b = encode_morton_15((10, 10).into()); // numerically smaller than code_a let delta_b = code_b .cast_signed() .wrapping_sub(code_a.cast_signed()) .cast_unsigned(); assert_eq!( MORTON.decode_delta(&[code_a, delta_b], &mut dec()).unwrap(), MORTON.decode_codes(&[code_a, code_b], &mut dec()).unwrap() ); } /// Compute expected decoded `[x0, y0, x1, y1, ...]` from raw (pre-shift) coordinate pairs. fn expected_coords(pairs: &[Coord]) -> Vec { pairs .iter() .flat_map(|&Coord { x, y }| { [ x.cast_signed() - COORD_SHIFT.cast_signed(), y.cast_signed() - COORD_SHIFT.cast_signed(), ] }) .collect() } /// Compute wrapping signed deltas between consecutive Morton codes. fn signed_deltas(codes: &[u32]) -> Vec { let mut prev = 0i32; codes .iter() .map(|&c| { let delta = c.cast_signed().wrapping_sub(prev).cast_unsigned(); prev = c.cast_signed(); delta }) .collect() } } ================================================ FILE: rust/mlt-core/src/codecs/rle.rs ================================================ use num_traits::PrimInt; use crate::utils::AsUsize as _; use crate::{Decoder, MltError, MltResult}; /// Generic run-length encode: returns `(run_lengths, values)`. #[must_use] pub fn encode_rle(data: &[T]) -> (Vec, Vec) { if data.is_empty() { return (Vec::new(), Vec::new()); } let mut runs = Vec::new(); let mut values = Vec::new(); let mut current_val = data[0]; let mut current_run = T::one(); for &val in &data[1..] { if val == current_val { current_run = current_run.saturating_add(T::one()); } else { runs.push(current_run); values.push(current_val); current_val = val; current_run = T::one(); } } runs.push(current_run); values.push(current_val); (runs, values) } /// Encode byte-level RLE as used in ORC for boolean and present streams. /// /// Format: control byte determines the run type: /// - `control >= 128`: literal run of `(256 - control)` bytes follow /// - `control < 128`: repeating run of `(control + 3)` copies of the next byte pub fn encode_byte_rle<'a>(data: &[u8], target: &'a mut Vec) -> &'a [u8] { target.clear(); let mut pos = 0; while pos < data.len() { // Look ahead for repeating run let mut repeat_count = 1; while pos + repeat_count < data.len() && data[pos + repeat_count] == data[pos] && repeat_count < 130 { repeat_count += 1; } if repeat_count >= 3 { // Encode repeating run #[expect(clippy::cast_possible_truncation, reason = "3 <= repeat_count < 130")] let control = (repeat_count - 3) as u8; target.push(control); target.push(data[pos]); pos += repeat_count; } else { // Encode literal run let mut literal_count = 0; // Scan ahead to see where the next repeating run starts while pos + literal_count < data.len() && literal_count < 128 { let mut inner_repeat = 1; while let next_idx = pos + literal_count && next_idx + inner_repeat < data.len() && data[next_idx + inner_repeat] == data[next_idx] && inner_repeat < 3 { inner_repeat += 1; } if inner_repeat >= 3 { break; } literal_count += 1; } #[expect( clippy::cast_possible_truncation, reason = "literal_count is always smaller than 128" )] let control = (256 - literal_count) as u8; target.push(control); target.extend_from_slice(&data[pos..pos + literal_count]); pos += literal_count; } } target } /// Decode byte-level RLE as used in ORC for boolean and present streams. /// /// Format: control byte determines the run type: /// - `control >= 128`: literal run of `(256 - control)` bytes follow /// - `control < 128`: repeating run of `(control + 3)` copies of the next byte pub fn decode_byte_rle(input: &[u8], num_bytes: usize, dec: &mut Decoder) -> MltResult> { let mut output = dec.alloc(num_bytes)?; let mut pos = 0; while output.len() < num_bytes && pos < input.len() { let control = input[pos]; pos += 1; if control >= 128 { let count = u32::from(control ^ 0xFF) + 1; let end = pos + count.as_usize(); let slice = input.get(pos..end).ok_or(MltError::BufferUnderflow( count, input.len().saturating_sub(pos), ))?; output.extend_from_slice(slice); pos = end; } else { let count = usize::from(control) + 3; let &value = input.get(pos).ok_or(MltError::BufferUnderflow(1, 0))?; pos += 1; output.extend(std::iter::repeat_n(value, count)); } } dec.adjust_alloc(&output, num_bytes)?; Ok(output) } #[cfg(test)] mod tests { use proptest::prelude::*; use super::*; use crate::decoder::RleMeta; use crate::test_helpers::dec; proptest! { #[test] fn test_rle_roundtrip_u32(data: Vec) { let (runs, vals) = encode_rle(&data); let mut combined = runs.clone(); combined.extend(vals); let runs = u32::try_from(runs.len()).unwrap(); let num_rle_values = u32::try_from(data.len()).unwrap(); let rle = RleMeta { runs, num_rle_values }; let decoded = rle.decode(&combined, &mut dec()).unwrap(); prop_assert_eq!(data, decoded); } #[test] fn test_byte_rle_roundtrip(data: Vec) { let mut encoded = Vec::new(); let buf = encode_byte_rle(&data, &mut encoded); let decoded = decode_byte_rle(buf, data.len(), &mut dec()).unwrap(); prop_assert_eq!(data, decoded); } } #[test] fn test_encode_rle_empty() { let (runs, vals) = encode_rle::(&[]); assert!(runs.is_empty()); assert!(vals.is_empty()); } #[test] fn test_encode_byte_rle_empty() { let mut buf = Vec::new(); assert!(encode_byte_rle(&[], &mut buf).is_empty()); } #[test] fn test_decode_byte_rle_empty() { assert!(decode_byte_rle(&[], 0, &mut dec()).unwrap().is_empty()); } } ================================================ FILE: rust/mlt-core/src/codecs/varint.rs ================================================ use integer_encoding::VarInt; use crate::utils::AsUsize as _; use crate::{Decoder, MltError, MltRefResult}; /// Parse a single varint (variable-length integer) from the input, returning /// the remaining bytes and the decoded value. /// /// Validates canonical encoding: a multibyte varint must not have a trailing /// zero byte (which would mean it could have been encoded in fewer bytes). #[inline] pub fn parse_varint(input: &[u8]) -> MltRefResult<'_, T> { match T::decode_var(input) { Some((value, consumed)) => { // A varint is canonical if its last byte is non-zero (for multibyte encodings). // Value 0 must be encoded as a single 0x00 byte. // For multibyte VarInts, the last byte (without a continuation bit) must be non-zero. // Using more bytes than necessary violates roundtrip-ability of MLT. if consumed > 1 && input[consumed - 1] == 0 { return Err(MltError::NonCanonicalVarInt); } Ok((&input[consumed..], value)) } None => Err(MltError::BufferUnderflow( u32::try_from(input.len().saturating_add(1))?, input.len(), )), } } /// Parse `size` varints of wire type `T` into a `Vec`, charging `dec` for /// the output allocation. pub fn parse_varint_vec<'a, T, U>( mut input: &'a [u8], size: u32, dec: &mut Decoder, ) -> MltRefResult<'a, Vec> where T: VarInt, U: TryFrom, MltError: From<>::Error>, { let mut values = dec.alloc::(size.as_usize())?; let mut val; for _ in 0..size { (input, val) = parse_varint::(input)?; values.push(val.try_into()?); } Ok((input, values)) } #[cfg(test)] mod tests { use rstest::rstest; use super::*; use crate::MltResult; use crate::test_helpers::dec; #[rstest] #[case::trailing_bytes(&[0x80, 0x01, 0x42], Ok((vec![0x42_u8], 128)))] #[case::zero(&[0x00], Ok((vec![], 0)))] #[case::max_single_byte(&[0x7F], Ok((vec![], 127)))] #[case::min_two_byte(&[0x80, 0x01], Ok((vec![], 128)))] #[case::max_two_byte(&[0xFF, 0x7F], Ok((vec![], 16383)))] #[case::min_three_byte(&[0x80, 0x80, 0x01], Ok((vec![], 16384)))] #[case::non_canonical_two(&[0x82, 0x00], Err(MltError::NonCanonicalVarInt))] #[case::non_canonical_three_byte(&[0x80, 0x80, 0x00], Err(MltError::NonCanonicalVarInt))] #[case::single_byte_with_trailing(&[0x01, 0x02, 0x03], Ok((vec![2, 3], 1)))] #[case::underflow(&[0x80, 0x80, 0x80], Err(MltError::BufferUnderflow(4, 3)))] fn test_varint_parsing(#[case] bytes: &[u8], #[case] expected: MltResult<(Vec, u32)>) { let actual = parse_varint::(bytes); // matching because MltError cannot implement PartialEq // effectively assert_eq!(actual, expected); match (actual, expected) { (Ok((v1, s1)), Ok((v2, s2))) => assert_eq!((v1, s1), (v2.as_slice(), s2)), (Err(actual), Err(expected)) => assert_eq!(actual.to_string(), expected.to_string()), (Ok(_), Err(_)) | (Err(_), Ok(_)) => panic!("Unexpected result"), } } #[test] fn test_parse_varint_vec() { // Encode [1u32, 2, 3] as varints and parse back. let mut buf = Vec::new(); let mut buf_tmp = vec![0u8; 10]; for v in [1u32, 2, 3] { let written = v.encode_var(&mut buf_tmp); buf.extend_from_slice(&buf_tmp[0..written]); } let (remaining, values) = parse_varint_vec::(&buf, 3, &mut dec()).expect("parse_varint_vec failed"); assert!(remaining.is_empty()); assert_eq!(values, [1, 2, 3]); } } ================================================ FILE: rust/mlt-core/src/codecs/zigzag.rs ================================================ use num_traits::{AsPrimitive, WrappingAdd, WrappingSub}; use zigzag::ZigZag; use crate::MltError::InvalidPairStreamSize; use crate::{Decoder, MltResult}; /// ZigZag-encode `data` into `target`. /// /// `target` is treated as a scratch buffer: cleared before writing. pub fn encode_zigzag<'a, T: ZigZag>(data: &[T], target: &'a mut Vec) -> &'a [T::UInt] { target.clear(); target.extend(data.iter().map(|&v| T::encode(v))); target } /// Delta-then-ZigZag-encode `data` into `target` in a single pass. /// /// `target` is treated as a scratch buffer: cleared before writing. /// Fuses the delta and zigzag steps to avoid an intermediate allocation. pub fn encode_zigzag_delta<'a, T: Copy + ZigZag + WrappingSub>( data: &[T], target: &'a mut Vec, ) -> &'a [T::UInt] { target.clear(); target.reserve(data.len()); let mut prev = T::zero(); for &v in data { target.push(T::encode(v.wrapping_sub(&prev))); prev = v; } target } /// Encode signed integer vec2 values using componentwise delta + zigzag into `target`. /// /// Input: `[x0, y0, x1, y1, ...]` /// Output: `[zigzag(x0-0), zigzag(y0-0), zigzag(x1-x0), zigzag(y1-y0), ...]` /// /// `target` is treated as a scratch buffer: cleared before writing. /// This is the inverse of `decode_componentwise_delta_vec2s`. pub fn encode_componentwise_delta_vec2s<'a, T>( data: &[T], target: &'a mut Vec, ) -> &'a [T::UInt] where T: ZigZag + WrappingSub, { target.clear(); target.reserve(data.len()); let mut prev_x = T::zero(); let mut prev_y = T::zero(); for chunk in data.chunks_exact(2) { let (x, y) = (chunk[0], chunk[1]); target.push(T::encode(x.wrapping_sub(&prev_x))); target.push(T::encode(y.wrapping_sub(&prev_y))); (prev_x, prev_y) = (x, y); } target } /// ZigZag-decode a slice, charging `dec` for the output allocation. pub fn decode_zigzag(data: &[T::UInt], dec: &mut Decoder) -> MltResult> { dec.consume_items::(data.len())?; Ok(data.iter().map(|&v| T::decode(v)).collect()) } /// Decode a vector of ZigZag-encoded unsigned deltas, charging `dec` for the output allocation. pub fn decode_zigzag_delta, U: 'static + Copy>( data: &[T::UInt], dec: &mut Decoder, ) -> MltResult> { dec.consume_items::(data.len())?; Ok(data .iter() .scan(T::zero(), |state, &v| { *state = state.wrapping_add(&T::decode(v)); Some((*state).as_()) }) .collect()) } /// Decode ([`ZigZag`] + delta) for Vec2s, charging `dec` for the output allocation. // TODO: The encoded process is (delta + ZigZag) for each component pub fn decode_componentwise_delta_vec2s( data: &[T::UInt], dec: &mut Decoder, ) -> MltResult> { if data.is_empty() || !data.len().is_multiple_of(2) { return Err(InvalidPairStreamSize(data.len())); } let alloc_size = data.len(); let mut result = dec.alloc(alloc_size)?; let mut last1 = T::zero(); let mut last2 = T::zero(); for i in (0..data.len()).step_by(2) { last1 = last1.wrapping_add(&T::decode(data[i])); last2 = last2.wrapping_add(&T::decode(data[i + 1])); result.push(last1); result.push(last2); } dec.adjust_alloc(&result, alloc_size)?; Ok(result) } #[cfg(test)] mod tests { use proptest::prelude::*; use super::*; use crate::test_helpers::dec; proptest! { #[test] fn test_zigzag_roundtrip_i64(data: Vec) { let mut encoded = Vec::new(); let decoded = decode_zigzag::(encode_zigzag(&data, &mut encoded), &mut dec()).unwrap(); prop_assert_eq!(data, decoded); } #[test] fn test_delta_roundtrip_i32(data: Vec) { if data.is_empty() { return Ok(()); } let mut encoded = Vec::new(); encode_zigzag_delta(&data, &mut encoded); let decoded: Vec = decode_zigzag_delta::(&encoded, &mut dec()).unwrap(); prop_assert_eq!(data, decoded); } #[test] fn test_componentwise_delta_vec2s(data: Vec) { if data.len() <= 1 { return Err(TestCaseError::reject("data not valid vertices")) } // done this way to not have to reject less let data_slice = if data.len().is_multiple_of(2) { &data } else { &data[..data.len() - 1] }; let mut encoded = Vec::new(); let data = encode_componentwise_delta_vec2s(data_slice, &mut encoded); let decoded = decode_componentwise_delta_vec2s::(data, &mut dec()).unwrap(); prop_assert_eq!(data_slice, &decoded); } } #[test] fn test_encode_zigzag_empty() { let mut target = Vec::::new(); assert!(encode_zigzag::(&[], &mut target).is_empty()); } #[test] fn test_encode_zigzag_delta_empty() { let mut target = Vec::::new(); encode_zigzag_delta::(&[], &mut target); assert!(target.is_empty()); } #[test] fn test_decode_zigzag_i32() { let encoded_u32 = [0u32, 1, 2, 3, 4, 5, u32::MAX]; let expected_i32 = [0i32, -1, 1, -2, 2, -3, i32::MIN]; let decoded_i32 = decode_zigzag::(&encoded_u32, &mut dec()).unwrap(); assert_eq!(decoded_i32, expected_i32); } #[test] fn test_decode_zigzag_i64() { let encoded_u64 = [0u64, 1, 2, 3, 4, 5, u64::MAX]; let expected_i64 = [0i64, -1, 1, -2, 2, -3, i64::MIN]; let decoded_i64 = decode_zigzag::(&encoded_u64, &mut dec()).unwrap(); assert_eq!(decoded_i64, expected_i64); } #[test] fn test_decode_zigzag_empty() { assert!(decode_zigzag::(&[], &mut dec()).unwrap().is_empty()); } #[test] fn test_decode_zigzag_delta_empty() { assert!( decode_zigzag_delta::(&[], &mut dec()) .unwrap() .is_empty() ); } #[test] fn test_decode_componentwise_delta_vec2s() { let values = &[1_u32, 2, 3, 4]; let decoded = decode_componentwise_delta_vec2s::(values, &mut dec()).unwrap(); assert_eq!(&decoded, &[-1_i32, 1, -3, 3]); } } ================================================ FILE: rust/mlt-core/src/convert/geojson.rs ================================================ //! `GeoJSON` -like data to represent decoded MLT data with i32 coordinates use std::collections::BTreeMap; use std::str::FromStr; use geo_types::Geometry; use serde::ser::SerializeMap as _; use serde::{Deserialize, Serialize}; use serde_json::{Number, Value}; use crate::decoder::{Layer, PropValueRef}; use crate::{LendingIterator, MltResult, ParsedLayer}; /// `GeoJSON` [`FeatureCollection`] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct FeatureCollection { #[serde(rename = "type")] pub ty: String, pub features: Vec, } impl FeatureCollection { /// Convert already-decoded layers to a `GeoJSON` [`FeatureCollection`], consuming them. /// Make sure to call `decode_all` on Layer before calling this (won't compile otherwise) pub fn from_layers<'a>(layers: impl IntoIterator>) -> MltResult { let mut features = Vec::new(); for layer in layers { let Layer::Tag01(parsed) = layer else { continue; }; let layer_name = parsed.name; let extent = parsed.extent; let mut feat_iter = parsed.iter_features(); while let Some(feat) = feat_iter.next() { let feat = feat?; let mut properties = BTreeMap::new(); for p in feat.iter_properties() { properties.insert(p.name.to_string(), p.value.into()); } properties.insert("_layer".into(), Value::String(layer_name.to_string())); properties.insert("_extent".into(), Value::Number(extent.into())); features.push(Feature { geometry: feat.geometry, id: feat.id, properties, ty: "Feature".into(), }); } } Ok(Self { features, ty: "FeatureCollection".into(), }) } pub fn equals(&self, other: &Self) -> Result { let self_val = normalize_tiny_floats(serde_json::to_value(self)?); let other_val = normalize_tiny_floats(serde_json::to_value(other)?); Ok(json_values_equal(&self_val, &other_val)) } } impl FromStr for FeatureCollection { type Err = serde_json::Error; fn from_str(s: &str) -> Result { serde_json::from_str(s) } } /// `GeoJSON` [`Feature`] #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct Feature { #[serde(with = "geom_serde")] pub geometry: Geometry, #[serde(skip_serializing_if = "Option::is_none")] pub id: Option, pub properties: BTreeMap, #[serde(rename = "type")] pub ty: String, } struct Geom32Wire<'a>(&'a Geometry); impl Serialize for Geom32Wire<'_> { fn serialize(&self, s: S) -> Result { geom_serde::serialize(self.0, s) } } /// Serialize with the preferred order of the keys impl Serialize for Feature { fn serialize(&self, serializer: S) -> Result { let len = 3 + usize::from(self.id.is_some()); let mut map = serializer.serialize_map(Some(len))?; map.serialize_entry("type", &self.ty)?; if let Some(id) = self.id { map.serialize_entry("id", &id)?; } map.serialize_entry("properties", &self.properties)?; map.serialize_entry("geometry", &Geom32Wire(&self.geometry))?; map.end() } } /// Serialize/deserialize [`Geometry`](geo_types::Geometry) in `GeoJSON` wire format: /// `{"type":"…","coordinates":…}` with `[x, y]` integer arrays. mod geom_serde { use geo_types::{ Geometry, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, }; use serde::de::Error as _; use serde::ser::{Error, SerializeMap as _}; use serde::{Deserialize, Deserializer, Serializer}; use serde_json::Value; type Arr = [i32; 2]; fn ls_arr(ls: &LineString) -> Vec { ls.0.iter().copied().map(Into::into).collect() } fn poly_arr(poly: &Polygon) -> Vec> { std::iter::once(poly.exterior()) .chain(poly.interiors()) .map(ls_arr) .collect() } fn arr_ls(v: Vec) -> LineString { LineString::from(v) } fn arr_poly(rings: Vec>) -> Polygon { let mut it = rings.into_iter(); let ext = it.next().map_or_else(|| LineString(vec![]), arr_ls); Polygon::new(ext, it.map(arr_ls).collect()) } pub fn serialize(g: &Geometry, s: S) -> Result { let mut m = s.serialize_map(Some(2))?; let (ty, coords): (&str, Value) = match g { Geometry::Point(p) => ("Point", serde_json::to_value(Arr::from(*p)).unwrap()), Geometry::LineString(ls) => ("LineString", serde_json::to_value(ls_arr(ls)).unwrap()), Geometry::Polygon(poly) => ("Polygon", serde_json::to_value(poly_arr(poly)).unwrap()), Geometry::MultiPoint(mp) => ( "MultiPoint", serde_json::to_value(mp.0.iter().copied().map(Arr::from).collect::>()) .unwrap(), ), Geometry::MultiLineString(mls) => ( "MultiLineString", serde_json::to_value(mls.iter().map(ls_arr).collect::>()).unwrap(), ), Geometry::MultiPolygon(mpoly) => ( "MultiPolygon", serde_json::to_value(mpoly.iter().map(poly_arr).collect::>()).unwrap(), ), _ => return Err(Error::custom("unsupported geometry variant")), }; m.serialize_entry("type", ty)?; m.serialize_entry("coordinates", &coords)?; m.end() } pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result, D::Error> { fn parse(v: Value) -> Result { serde_json::from_value(v).map_err(E::custom) } #[derive(Deserialize)] struct Wire { #[serde(rename = "type")] ty: String, coordinates: Value, } let Wire { ty, coordinates: c } = Wire::deserialize(d)?; Ok(match ty.as_str() { "Point" => Geometry::Point(Point::from(parse::(c)?)), "LineString" => Geometry::LineString(arr_ls(parse(c)?)), "Polygon" => Geometry::Polygon(arr_poly(parse(c)?)), "MultiPoint" => { let v: Vec = parse(c)?; Geometry::MultiPoint(MultiPoint(v.into_iter().map(Point::from).collect())) } "MultiLineString" => { let v: Vec> = parse(c)?; Geometry::MultiLineString(MultiLineString(v.into_iter().map(arr_ls).collect())) } "MultiPolygon" => { let v: Vec>> = parse(c)?; Geometry::MultiPolygon(MultiPolygon(v.into_iter().map(arr_poly).collect())) } _ => { return Err(D::Error::unknown_variant( &ty, &[ "Point", "LineString", "Polygon", "MultiPoint", "MultiLineString", "MultiPolygon", ], )); } }) } } /// Convert f32 to `GeoJSON` value: finite as number, non-finite as string per issue #978. #[must_use] pub fn f32_to_json(f: f32) -> Value { if f.is_nan() { Value::String("f32::NAN".to_owned()) } else if f == f32::INFINITY { Value::String("f32::INFINITY".to_owned()) } else if f == f32::NEG_INFINITY { Value::String("f32::NEG_INFINITY".to_owned()) } else { Number::from_f64(f64::from(f)).expect("finite f32").into() } } /// Convert f64 to `GeoJSON` value: finite as number, non-finite as string per issue #978. #[must_use] pub fn f64_to_json(f: f64) -> Value { if f.is_nan() { Value::String("f64::NAN".to_owned()) } else if f == f64::INFINITY { Value::String("f64::INFINITY".to_owned()) } else if f == f64::NEG_INFINITY { Value::String("f64::NEG_INFINITY".to_owned()) } else { Number::from_f64(f).expect("finite f64").into() } } impl From> for Value { fn from(v: PropValueRef<'_>) -> Self { match v { PropValueRef::Bool(v) => Self::Bool(v), PropValueRef::I8(v) => Self::from(v), PropValueRef::U8(v) => Self::from(v), PropValueRef::I32(v) => Self::from(v), PropValueRef::U32(v) => Self::from(v), PropValueRef::I64(v) => Self::from(v), PropValueRef::U64(v) => Self::from(v), PropValueRef::F32(v) => f32_to_json(v), PropValueRef::F64(v) => f64_to_json(v), PropValueRef::Str(s) => Self::String(s.to_string()), } } } /// Replace tiny float values (e.g. `1e-40`) with `0.0` to handle codec precision issues. fn normalize_tiny_floats(value: Value) -> Value { match value { Value::Number(ref n) => { let eps = f64::from(f32::EPSILON); if let Some(f) = n.as_f64() && f.is_finite() && f.abs() < eps { Value::from(0.0) } else { value } } Value::Array(arr) => Value::Array(arr.into_iter().map(normalize_tiny_floats).collect()), Value::Object(obj) => Value::Object( obj.into_iter() .map(|(k, v)| (k, normalize_tiny_floats(v))) .collect(), ), v => v, } } /// Compare two JSON values for equality. Numbers are compared with float tolerance so that /// f32 round-trip (e.g. 3.14 vs 3.140000104904175) and Java minimal decimal (e.g. 3.4028235e+38) /// match the Rust decoder output. fn json_values_equal(a: &Value, b: &Value) -> bool { match (a, b) { (Value::Number(na), Value::Number(nb)) if na.is_f64() && nb.is_f64() => { let na = na.as_f64().expect("f64"); let nb = nb.as_f64().expect("f64"); assert!( !na.is_nan() && !nb.is_nan(), "unexpected non-finite numbers" ); let abs_diff = (na - nb).abs(); let max_abs = na.abs().max(nb.abs()).max(1.0); abs_diff <= f64::from(f32::EPSILON) * max_abs * 2.0 } (Value::Array(aa), Value::Array(ab)) => { aa.len() == ab.len() && aa .iter() .zip(ab.iter()) .all(|(x, y)| json_values_equal(x, y)) } (Value::Object(ao), Value::Object(bo)) => { ao.len() == bo.len() && ao .iter() .all(|(k, v)| bo.get(k).is_some_and(|w| json_values_equal(v, w))) } _ => a == b, } } ================================================ FILE: rust/mlt-core/src/convert/mod.rs ================================================ pub mod geojson; pub mod mvt; ================================================ FILE: rust/mlt-core/src/convert/mvt.rs ================================================ //! Convert MVT data to [`FeatureCollection`] or to [`TileLayer`] use std::collections::{BTreeMap, HashMap}; use geo_types::{ Coord, Geometry as Geom, Geometry, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, }; use mvt_reader::Reader; use mvt_reader::feature::{Feature as MvtFeature, Value as MvtValue}; use serde_json::{Number, Value}; use crate::decoder::{PropValue, TileFeature, TileLayer}; use crate::geojson::{Feature, FeatureCollection}; use crate::{MltError, MltResult}; // ── Common MVT parsing ──────────────────────────────────────────────────────── /// Parsed representation of a single MVT layer: metadata plus raw features. struct MvtLayer { name: String, extent: u32, features: Vec>, } /// Parse MVT bytes into a list of layers, each holding its raw features. /// /// This is the single place where the `mvt_reader` API is called; both /// [`mvt_to_feature_collection`] and [`mvt_to_tile_layers`] build on top of it. fn read_mvt_layers(data: Vec) -> MltResult> { let reader = Reader::new(data).map_err(|e| MltError::MvtParse(e.to_string()))?; let metas = reader .get_layer_metadata() .map_err(|e| MltError::MvtParse(e.to_string()))?; metas .iter() .map(|meta| { let features = reader .get_features(meta.layer_index) .map_err(|e| MltError::MvtParse(e.to_string()))?; Ok(MvtLayer { name: meta.name.clone(), extent: meta.extent, features, }) }) .collect() } /// Parse MVT binary data and convert to a [`FeatureCollection`]. pub fn mvt_to_feature_collection(data: Vec) -> MltResult { let mut features = Vec::new(); for layer in read_mvt_layers(data)? { for feat in layer.features { let geometry = convert_geometry(&feat.geometry)?; let mut properties = feat .properties .map(|p| { p.into_iter() .map(|(k, v)| (k, convert_value(&v))) .collect::>() }) .unwrap_or_default(); properties.insert("_layer".into(), Value::String(layer.name.clone())); properties.insert("_extent".into(), Value::Number(layer.extent.into())); features.push(Feature { geometry, id: feat.id, properties, ty: "Feature".into(), }); } } Ok(FeatureCollection { features, ty: "FeatureCollection".into(), }) } /// Parse MVT binary data and convert each layer to a row-oriented [`TileLayer`]. /// /// Each MVT layer becomes one [`TileLayer`]. Property column types are inferred /// from all features in the layer: the first non-null value seen for each column /// determines its type, with `I64`+`U64` widened to `I64` and `F32`+`F64` widened /// to `F64`; all other type conflicts fall back to `Str`. pub fn mvt_to_tile_layers(data: Vec) -> MltResult> { read_mvt_layers(data)? .into_iter() .map(mvt_layer_to_tile) .collect() } fn mvt_layer_to_tile(layer: MvtLayer) -> MltResult { // First pass: collect property names (insertion-ordered) and infer column types. let mut col_names: Vec = Vec::new(); let mut col_index: HashMap = HashMap::new(); let mut col_types: Vec = Vec::new(); for feat in &layer.features { let Some(props) = &feat.properties else { continue; }; for (key, val) in props { let idx = *col_index.entry(key.clone()).or_insert_with(|| { let i = col_names.len(); col_names.push(key.clone()); col_types.push(InferredType::Unknown); i }); col_types[idx] = col_types[idx].merge(InferredType::from_mvt(val)); } } // Columns that were only ever null fall back to Str. for t in &mut col_types { if *t == InferredType::Unknown { *t = InferredType::Str; } } // Second pass: build TileFeature objects. let mut tile_features = Vec::with_capacity(layer.features.len()); for feat in layer.features { let geometry = convert_geometry(&feat.geometry)?; // Start every slot with a typed null; fill in present values below. let mut properties: Vec = col_types.iter().map(|t| t.typed_null()).collect(); if let Some(props) = feat.properties { for (key, val) in props { if let Some(&idx) = col_index.get(&key) && !matches!(val, MvtValue::Null) { properties[idx] = col_types[idx].convert(val); } } } tile_features.push(TileFeature { id: feat.id, geometry, properties, }); } Ok(TileLayer { name: layer.name, extent: layer.extent, property_names: col_names, features: tile_features, }) } fn coord(c: impl AsRef>) -> Coord { let c = c.as_ref(); #[expect(clippy::cast_possible_truncation)] Coord { x: c.x.round() as i32, y: c.y.round() as i32, } } fn convert_geometry(geom: &Geom) -> MltResult> { Ok(match geom { Geom::Point(v) => Geometry::::Point(Point(coord(v))), Geom::MultiPoint(v) => { Geometry::::MultiPoint(MultiPoint(v.iter().map(|p| Point(coord(p))).collect())) } Geom::LineString(v) => { Geometry::::LineString(LineString(v.coords().map(coord).collect())) } Geom::MultiLineString(v) => Geometry::::MultiLineString(MultiLineString( v.iter() .map(|ls| LineString(ls.coords().map(coord).collect())) .collect(), )), Geom::Polygon(v) => Geometry::::Polygon(convert_polygon(v)), Geom::MultiPolygon(v) => { Geometry::::MultiPolygon(MultiPolygon(v.iter().map(convert_polygon).collect())) } Geom::GeometryCollection(v) => { return if v.len() == 1 { convert_geometry(&v[0]) } else { Err(MltError::BadMvtGeometry( "multiple geometries in a collection are not supported", )) }; } Geom::Line(_) => Err(MltError::BadMvtGeometry("Unsupported Line geo type"))?, Geom::Rect(_) => Err(MltError::BadMvtGeometry("Unsupported Rect geo type"))?, Geom::Triangle(_) => Err(MltError::BadMvtGeometry("Unsupported Triangle geo type"))?, }) } fn convert_polygon(poly: &Polygon) -> Polygon { let exterior = LineString(poly.exterior().coords().map(coord).collect()); let interiors = poly .interiors() .iter() .map(|r| LineString(r.coords().map(coord).collect())) .collect(); Polygon::new(exterior, interiors) } fn convert_value(val: &MvtValue) -> Value { match val { MvtValue::String(s) => Value::String(s.clone()), MvtValue::Float(f) => Number::from_f64(f64::from(*f)).map_or(Value::Null, Value::Number), MvtValue::Double(f) => Number::from_f64(*f).map_or(Value::Null, Value::Number), MvtValue::Int(i) | MvtValue::SInt(i) => Value::Number((*i).into()), MvtValue::UInt(u) => Value::Number((*u).into()), MvtValue::Bool(b) => Value::Bool(*b), MvtValue::Null => Value::Null, } } /// Column type inferred from MVT property values across all features in a layer. #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum InferredType { Unknown, Bool, I64, U64, F32, F64, Str, } impl InferredType { fn from_mvt(val: &MvtValue) -> Self { match val { MvtValue::Bool(_) => Self::Bool, MvtValue::Int(_) | MvtValue::SInt(_) => Self::I64, MvtValue::UInt(_) => Self::U64, MvtValue::Float(_) => Self::F32, MvtValue::Double(_) => Self::F64, MvtValue::String(_) => Self::Str, MvtValue::Null => Self::Unknown, } } /// Merge with another type, widening when necessary. fn merge(self, other: Self) -> Self { if self == Self::Unknown { return other; } if other == Self::Unknown || self == other { return self; } if matches!( (self, other), (Self::I64, Self::U64) | (Self::U64, Self::I64) ) { return Self::I64; } if matches!( (self, other), (Self::F32, Self::F64) | (Self::F64, Self::F32) ) { return Self::F64; } Self::Str } fn typed_null(self) -> PropValue { match self { Self::Unknown | Self::Str => PropValue::Str(None), Self::Bool => PropValue::Bool(None), Self::I64 => PropValue::I64(None), Self::U64 => PropValue::U64(None), Self::F32 => PropValue::F32(None), Self::F64 => PropValue::F64(None), } } /// Convert an owned [`MvtValue`] into a [`PropValue`] matching this column type. fn convert(self, val: MvtValue) -> PropValue { match (self, val) { (_, MvtValue::Null) => self.typed_null(), (Self::Bool, MvtValue::Bool(b)) => PropValue::Bool(Some(b)), (Self::I64, MvtValue::Int(i) | MvtValue::SInt(i)) => PropValue::I64(Some(i)), (Self::I64, MvtValue::UInt(u)) if i64::try_from(u).is_ok() => { // Value must be within 0..i64::MAX #[expect(clippy::cast_possible_wrap, reason = "checked above")] PropValue::I64(Some(u as i64)) } (Self::U64, MvtValue::UInt(u)) => PropValue::U64(Some(u)), (Self::F32, MvtValue::Float(f)) => PropValue::F32(Some(f)), (Self::F64, MvtValue::Double(f)) => PropValue::F64(Some(f)), (Self::F64, MvtValue::Float(f)) => PropValue::F64(Some(f64::from(f))), (_, MvtValue::String(s)) => PropValue::Str(Some(s)), // Type conflict at runtime: fall back to a debug string. (_, v) => PropValue::Str(Some(format!("{v:?}"))), } } } ================================================ FILE: rust/mlt-core/src/decoder/analyze.rs ================================================ use crate::decoder::{ Geometry, GeometryType, GeometryValues, Id, Layer01, Property, RawFsstData, RawGeometry, RawId, RawIdValue, RawPlainData, RawPresence, RawProperty, RawScalar, RawSharedDict, RawSharedDictEncoding, RawSharedDictItem, RawStrings, RawStringsEncoding, StreamMeta, }; use crate::{Analyze, DecodeState, StatType}; impl<'a, S: DecodeState> Analyze for Layer01<'a, S> where Option>: Analyze, Geometry<'a, S>: Analyze, Vec>: Analyze, { fn collect_statistic(&self, stat: StatType) -> usize { match stat { StatType::DecodedMetaSize => self.name.len() + size_of::(), StatType::DecodedDataSize => { self.id.collect_statistic(stat) + self.geometry.collect_statistic(stat) + self.properties.collect_statistic(stat) } StatType::FeatureCount => self.geometry.collect_statistic(stat), } } fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { self.id.for_each_stream(cb); self.geometry.for_each_stream(cb); self.properties.for_each_stream(cb); } } impl Analyze for RawGeometry<'_> { fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { self.meta.for_each_stream(cb); self.items.for_each_stream(cb); } } impl Analyze for GeometryValues { fn collect_statistic(&self, stat: StatType) -> usize { match stat { StatType::DecodedDataSize => { self.vector_types.collect_statistic(stat) + self.geometry_offsets.collect_statistic(stat) + self.part_offsets.collect_statistic(stat) + self.ring_offsets.collect_statistic(stat) + self.index_buffer.collect_statistic(stat) + self.triangles.collect_statistic(stat) + self.vertices.collect_statistic(stat) } StatType::DecodedMetaSize => 0, StatType::FeatureCount => self.vector_types.len(), } } } impl Analyze for GeometryType { fn collect_statistic(&self, _stat: StatType) -> usize { size_of::() } } impl Analyze for RawId<'_> { fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { self.presence.for_each_stream(cb); self.value.for_each_stream(cb); } } impl Analyze for RawIdValue<'_> { fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { match self { Self::Id32(v) | Self::Id64(v) => v.for_each_stream(cb), } } } impl Analyze for RawPresence<'_> { fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { self.0.for_each_stream(cb); } } impl Analyze for RawPlainData<'_> { fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { self.lengths.for_each_stream(cb); self.data.for_each_stream(cb); } } impl Analyze for RawFsstData<'_> { fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { self.symbol_lengths.for_each_stream(cb); self.symbol_table.for_each_stream(cb); self.lengths.for_each_stream(cb); self.corpus.for_each_stream(cb); } } impl Analyze for RawStringsEncoding<'_> { fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { match self { Self::Plain(plain_data) => plain_data.for_each_stream(cb), Self::Dictionary { plain_data, offsets, } => { plain_data.for_each_stream(cb); offsets.for_each_stream(cb); } Self::FsstPlain(fsst_data) => fsst_data.for_each_stream(cb), Self::FsstDictionary { fsst_data, offsets } => { fsst_data.for_each_stream(cb); offsets.for_each_stream(cb); } } } } impl Analyze for RawScalar<'_> { fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { self.presence.for_each_stream(cb); self.data.for_each_stream(cb); } } impl Analyze for RawStrings<'_> { fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { self.presence.for_each_stream(cb); self.encoding.for_each_stream(cb); } } impl Analyze for RawSharedDictItem<'_> { fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { self.presence.for_each_stream(cb); self.data.for_each_stream(cb); } } impl Analyze for RawSharedDictEncoding<'_> { fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { match self { Self::Plain(plain_data) => plain_data.for_each_stream(cb), Self::FsstPlain(fsst_data) => fsst_data.for_each_stream(cb), } } } impl Analyze for RawSharedDict<'_> { fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { self.encoding.for_each_stream(cb); self.children.for_each_stream(cb); } } impl Analyze for RawProperty<'_> { fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { match self { Self::Bool(s) | Self::I8(s) | Self::U8(s) | Self::I32(s) | Self::U32(s) | Self::I64(s) | Self::U64(s) | Self::F32(s) | Self::F64(s) => s.for_each_stream(cb), Self::Str(s) => s.for_each_stream(cb), Self::SharedDict(s) => s.for_each_stream(cb), } } } ================================================ FILE: rust/mlt-core/src/decoder/column.rs ================================================ use std::io; use std::io::Write; use crate::MltError::ParsingColumnType; use crate::decoder::{Column, ColumnType}; use crate::utils::{BinarySerializer as _, parse_string, parse_u8}; use crate::{MltRefResult, Parser}; impl Column<'_> { /// Parse a single column definition pub(crate) fn from_bytes<'a>( input: &'a [u8], _parser: &mut Parser, ) -> MltRefResult<'a, Column<'a>> { let (mut input, typ) = ColumnType::from_bytes(input)?; let name = if typ.has_name() { let pair = parse_string(input)?; input = pair.0; Some(pair.1) } else { None }; Ok(( input, Column { typ, name, children: Vec::new(), }, )) } } impl ColumnType { /// Parse a column type from u8 pub(crate) fn from_bytes(input: &[u8]) -> MltRefResult<'_, Self> { let (input, value) = parse_u8(input)?; let value = Self::try_from(value).or(Err(ParsingColumnType(value)))?; Ok((input, value)) } pub(crate) fn write_to(self, writer: &mut W) -> io::Result<()> { writer.write_u8(self as u8)?; Ok(()) } /// Returns true if the column definition includes a name field in the serialized format. /// Note: ID and Geometry columns use implicit naming and do not include a name field. #[must_use] pub(crate) fn has_name(self) -> bool { !matches!( self, Self::Id | Self::OptId | Self::LongId | Self::OptLongId | Self::Geometry ) } /// Check if the column type has a presence stream #[must_use] pub(crate) fn is_optional(self) -> bool { (self as u8) & 1 != 0 } } ================================================ FILE: rust/mlt-core/src/decoder/fuzzing.rs ================================================ use arbitrary::{Arbitrary, Result, Unstructured}; use geo_types::{Coord, Geometry, Point}; #[cfg(fuzzing)] use crate::decoder::ColumnType; use crate::decoder::GeometryValues; #[allow( unused_imports, clippy::wildcard_imports, reason = "not worth for fuzzing" )] use crate::*; #[cfg(fuzzing)] /// To make sure we serialize out in the same order as the original file, we need to store the order in which we parsed the columns #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum LayerOrdering { Id, Geometry, Property, } #[cfg(fuzzing)] impl From for LayerOrdering { fn from(typ: ColumnType) -> Self { use ColumnType::*; match typ { OptId | Id | LongId | OptLongId => Self::Id, Bool | OptBool | I8 | OptI8 | U8 | OptU8 | I32 | OptI32 | U32 | OptU32 | I64 | OptI64 | U64 | OptU64 | F32 | OptF32 | F64 | OptF64 | Str | OptStr | SharedDict => { Self::Property } Geometry => Self::Geometry, } } } #[derive(Debug, Clone, PartialEq, PartialOrd, Arbitrary)] enum ArbitraryGeometry { Point((i32, i32)), // FIXME: Add LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, once supported upstream } impl From for Geometry { fn from(value: ArbitraryGeometry) -> Self { match value { ArbitraryGeometry::Point((x, y)) => Self::Point(Point(Coord:: { x, y })), // FIXME: once fully working, add the rest } } } impl Arbitrary<'_> for GeometryValues { fn arbitrary(u: &mut Unstructured<'_>) -> Result { // Bound geometry count to prevent OOM from unbounded iteration let count = u.int_in_range(1..=32u16)? as usize; let mut decoded = Self::default(); for _ in 0..count { let geo: ArbitraryGeometry = u.arbitrary()?; decoded.push_geom(&Geometry::::from(geo)); } Ok(decoded) } } ================================================ FILE: rust/mlt-core/src/decoder/geometry/decode.rs ================================================ use crate::codecs::varint::parse_varint; use crate::decoder::{ DictionaryType, GeometryType, GeometryValues, IntEncoding, LengthType, OffsetType, RawGeometry, RawStream, StreamMeta, StreamType, }; use crate::errors::AsMltError as _; use crate::utils::{AsUsize as _, SetOptionOnce as _}; use crate::{Decode, Decoder, MltError, MltResult, Parser}; /// Advance `offset` by `count` and extend `buffer` with the consecutive values /// `old_offset + 1, old_offset + 2, …, new_offset`. fn push_consecutive_offsets( buffer: &mut Vec, offset: &mut u32, count: usize, ) -> MltResult<()> { if count > 0 { let count = u32::try_from(count).or_overflow()?; *offset = offset.checked_add(count).or_overflow()?; // Safety: offset+1 cannot overflow because offset+count didn't and count >= 1. buffer.extend((*offset - count + 1)..=*offset); } Ok(()) } pub fn decode_geometry_types( meta: RawStream<'_>, dec: &mut Decoder, ) -> MltResult> { // TODO: simplify this, e.g. use u8 or even GeometryType directly rather than going via Vec let vector_types: Vec = meta.decode_u32s(dec)?; let vector_types: Vec = vector_types .into_iter() .map::, _>(|v| Ok(u8::try_from(v)?.try_into()?)) .collect::>()?; Ok(vector_types) } /// Handle the parsing of the different topology length buffers separate not generic to reduce the /// branching and improve the performance pub fn decode_root_length_stream( geometry_types: &[GeometryType], root_length_stream: &[u32], buffer_id: GeometryType, dec: &mut Decoder, ) -> MltResult> { let alloc_size = geometry_types.len().checked_add(1).or_overflow()?; let mut root_buffer_offsets = dec.alloc(alloc_size)?; root_buffer_offsets.push(0); let mut offset = 0_u32; let mut root_length_counter = 0_usize; for &geom_type in geometry_types { let increment = if geom_type > buffer_id { let val = *root_length_stream .get(root_length_counter) .ok_or(MltError::GeometryIndexOutOfBounds(root_length_counter))?; // Safety: counter bounded by `geometry_types.len()`, which fits in usize. root_length_counter += 1; val } else { 1 }; offset = offset.checked_add(increment).or_overflow()?; root_buffer_offsets.push(offset); } dec.adjust_alloc(&root_buffer_offsets, alloc_size)?; Ok(root_buffer_offsets) } /// Case where no ring buffer exists so no `MultiPolygon` or `Polygon` geometry is part of the buffer pub fn decode_level1_without_ring_buffer_length_stream( geometry_types: &[GeometryType], root_offset_buffer: &[u32], level1_length_buffer: &[u32], dec: &mut Decoder, ) -> MltResult> { // Safety: root_offset_buffer is produced by decode_root_length_stream, which always // pushes an initial 0, so it is never empty. let alloc_size = root_offset_buffer[root_offset_buffer.len() - 1] .as_usize() .checked_add(1) .or_overflow()?; let mut level1_buffer_offsets = dec.alloc(alloc_size)?; level1_buffer_offsets.push(0); let mut offset = 0_u32; let mut level1_length_counter = 0_usize; for (&geometry_type, w) in geometry_types.iter().zip(root_offset_buffer.windows(2)) { let num_geometries = w[1].checked_sub(w[0]).or_overflow()?.as_usize(); if geometry_type.is_linestring() { // For MultiLineString and LineString a value in the level1LengthBuffer exists for _j in 0..num_geometries { let val = *level1_length_buffer .get(level1_length_counter) .ok_or(MltError::GeometryIndexOutOfBounds(level1_length_counter))?; offset = offset.checked_add(val).or_overflow()?; level1_buffer_offsets.push(offset); // Safety: counter bounded by slice lengths, which fit in usize. level1_length_counter += 1; } } else { // For MultiPoint and Point no value in level1LengthBuffer exists push_consecutive_offsets(&mut level1_buffer_offsets, &mut offset, num_geometries)?; } } dec.adjust_alloc(&level1_buffer_offsets, alloc_size)?; Ok(level1_buffer_offsets) } pub fn decode_level1_length_stream( geometry_types: &[GeometryType], root_offset_buffer: &[u32], level1_length_buffer: &[u32], is_line_string_present: bool, dec: &mut Decoder, ) -> MltResult> { // Safety: root_offset_buffer is produced by decode_root_length_stream, which always // pushes an initial 0, so it is never empty. let alloc_size = root_offset_buffer[root_offset_buffer.len() - 1] .as_usize() .checked_add(1) .or_overflow()?; let mut level1_buffer_offsets = dec.alloc(alloc_size)?; level1_buffer_offsets.push(0); let mut offset = 0_u32; let mut level1_length_buffer_counter = 0_usize; for (&geometry_type, w) in geometry_types.iter().zip(root_offset_buffer.windows(2)) { let num_geometries = w[1].checked_sub(w[0]).or_overflow()?.as_usize(); if geometry_type.is_polygon() || (is_line_string_present && geometry_type.is_linestring()) { // For MultiPolygon, Polygon and in some cases for MultiLineString and LineString // a value in the level1LengthBuffer exists for _j in 0..num_geometries { let val = *level1_length_buffer .get(level1_length_buffer_counter) .ok_or(MltError::GeometryIndexOutOfBounds( level1_length_buffer_counter, ))?; offset = offset.checked_add(val).or_overflow()?; level1_buffer_offsets.push(offset); // Safety: counter bounded by slice lengths, which fit in usize. level1_length_buffer_counter += 1; } } else { // For MultiPoint and Point and in some cases for MultiLineString and LineString // no value in the level1LengthBuffer exists push_consecutive_offsets(&mut level1_buffer_offsets, &mut offset, num_geometries)?; } } dec.adjust_alloc(&level1_buffer_offsets, alloc_size)?; Ok(level1_buffer_offsets) } pub fn decode_level2_length_stream( geometry_types: &[GeometryType], root_offset_buffer: &[u32], level1_offset_buffer: &[u32], level2_length_buffer: &[u32], dec: &mut Decoder, ) -> MltResult> { // Safety: level1_offset_buffer is produced by decode_level1_*_length_stream, which // always pushes an initial 0, so it is never empty. let last = level1_offset_buffer[level1_offset_buffer.len() - 1]; let alloc_size = last.as_usize().checked_add(1).or_overflow()?; let mut level2_buffer_offsets = dec.alloc(alloc_size)?; level2_buffer_offsets.push(0); let mut previous_offset = 0_u32; let mut level1_tail = level1_offset_buffer; let mut level2_pos = 0_usize; for (&geometry_type, w) in geometry_types.iter().zip(root_offset_buffer.windows(2)) { let num_geometries = w[1].checked_sub(w[0]).or_overflow()?.as_usize(); if geometry_type != GeometryType::Point && geometry_type != GeometryType::MultiPoint { // For MultiPolygon, MultiLineString, Polygon and LineString a value in level2LengthBuffer // exists for _j in 0..num_geometries { let [base, next, ..] = *level1_tail else { return Err(MltError::IntegerOverflow); }; let num_parts = next.checked_sub(base).or_overflow()?.as_usize(); level1_tail = &level1_tail[1..]; for _k in 0..num_parts { let val = *level2_length_buffer .get(level2_pos) .ok_or(MltError::GeometryIndexOutOfBounds(level2_pos))?; previous_offset = previous_offset.checked_add(val).or_overflow()?; // Safety: counter bounded by slice lengths, which fit in usize. level2_pos += 1; level2_buffer_offsets.push(previous_offset); } } } else { // For MultiPoint and Point no value in level2LengthBuffer exists push_consecutive_offsets( &mut level2_buffer_offsets, &mut previous_offset, num_geometries, )?; if num_geometries > level1_tail.len() { return Err(MltError::IntegerOverflow); } level1_tail = &level1_tail[num_geometries..]; } } dec.adjust_alloc(&level2_buffer_offsets, alloc_size)?; Ok(level2_buffer_offsets) } impl<'a> RawGeometry<'a> { /// Parse encoded geometry from bytes (expects varint stream count + streams). /// Reserves decoded memory against the parser's budget. pub fn from_bytes(input: &'a [u8], parser: &mut Parser) -> crate::MltRefResult<'a, Self> { let (input, stream_count) = parse_varint::(input)?; let stream_count = stream_count.as_usize(); if stream_count == 0 { return Ok(( input, Self { meta: RawStream::new( StreamMeta::new( StreamType::Data(DictionaryType::None), IntEncoding::none(), 0, ), &[], ), items: Vec::new(), }, )); } let (input, meta) = RawStream::from_bytes(input, parser)?; // Safety: stream_count is validated != 0 let (input, items) = RawStream::parse_multiple(input, stream_count - 1, parser)?; Ok((input, Self { meta, items })) } } impl Decode for RawGeometry<'_> { /// Decode into [`GeometryValues`], charging `dec` before each `Vec` /// allocation. All streams carry `num_values` in their metadata so every /// charge is pre-hoc. fn decode(self, dec: &mut Decoder) -> MltResult { let RawGeometry { meta, items } = self; let vector_types = decode_geometry_types(meta, dec)?; let mut geometry_offsets: Option> = None; let mut part_offsets: Option> = None; let mut ring_offsets: Option> = None; let mut vertex_offsets: Option> = None; let mut index_buffer: Option> = None; let mut triangles: Option> = None; let mut vertices: Option> = None; for stream in items { match stream.meta.stream_type { StreamType::Present => {} StreamType::Data(v) => match v { DictionaryType::Vertex | DictionaryType::Morton => { vertices.set_once(stream.decode_i32s(dec)?)?; } _ => Err(MltError::UnexpectedStreamType(stream.meta.stream_type))?, }, StreamType::Offset(v) => { let target = match v { OffsetType::Vertex => &mut vertex_offsets, OffsetType::Index => &mut index_buffer, _ => Err(MltError::UnexpectedStreamType(stream.meta.stream_type))?, }; target.set_once(stream.decode_u32s(dec)?)?; } StreamType::Length(v) => { let target = match v { LengthType::Geometries => &mut geometry_offsets, LengthType::Parts => &mut part_offsets, LengthType::Rings => &mut ring_offsets, LengthType::Triangles => &mut triangles, _ => Err(MltError::UnexpectedStreamType(stream.meta.stream_type))?, }; target.set_once(stream.decode_u32s(dec)?)?; } } } if index_buffer.is_some() && part_offsets.is_none() { // Case when the indices of a Polygon outline are not encoded in the data so no // topology data are present in the tile // // return FlatGpuVector::new(vector_types, triangles, index_buffer, vertices); return Err(MltError::NotImplemented( "index_buffer.is_some() && part_offsets.is_none() case", )); } // Use decode_root_length_stream if geometry_offsets is present if let Some(offsets) = geometry_offsets.take() { geometry_offsets = Some(decode_root_length_stream( &vector_types, &offsets, GeometryType::Polygon, dec, )?); if let Some(part_offsets_copy) = part_offsets.take() { if let Some(ring_offsets_copy) = ring_offsets.take() { part_offsets = Some(decode_level1_length_stream( &vector_types, geometry_offsets.as_ref().unwrap(), &part_offsets_copy, false, // isLineStringPresent dec, )?); ring_offsets = Some(decode_level2_length_stream( &vector_types, geometry_offsets.as_ref().unwrap(), part_offsets.as_ref().unwrap(), &ring_offsets_copy, dec, )?); } else { part_offsets = Some(decode_level1_without_ring_buffer_length_stream( &vector_types, geometry_offsets.as_ref().unwrap(), &part_offsets_copy, dec, )?); } } } else if let Some(offsets) = part_offsets.take() { if let Some(ring_offsets_copy) = ring_offsets.take() { let is_line_string_present = vector_types.iter().any(|t| t.is_linestring()); part_offsets = Some(decode_root_length_stream( &vector_types, &offsets, GeometryType::LineString, dec, )?); ring_offsets = Some(decode_level1_length_stream( &vector_types, part_offsets.as_ref().unwrap(), &ring_offsets_copy, is_line_string_present, dec, )?); } else { part_offsets = Some(decode_root_length_stream( &vector_types, &offsets, GeometryType::Point, dec, )?); } } // Case when the indices of a Polygon outline are encoded in the tile // This is handled by including index_buffer in the GeometryValues // Expand vertex dictionary: // If a vertex offset stream was present, // - `vertices` holds only the unique dictionary entries and // - `vertex_offsets` holds per-vertex indices into it. // // Expand them into a single flat (x, y) sequence so that `GeometryValues` always // represents fully decoded data, regardless of the encoding that was used. if let Some(offsets) = vertex_offsets.take() && let Some(dict) = vertices.as_deref() { dec.consume_items::<[i32; 2]>(offsets.len())?; // SAFETY: // Check before multiplying: i < dict_vertex_count guarantees // i * 2 + 1 < dict.len() with no risk of overflow, because // Rust limits Vec::len() to isize::MAX, so // dict_vertex_count <= isize::MAX / 2, meaning // i * 2 + 1 <= isize::MAX < usize::MAX. let dict_vertex_count = dict.len() / 2; vertices = Some(offsets.iter().try_fold( Vec::with_capacity(offsets.len() * 2), |mut acc, &idx| -> MltResult<_> { let i = idx.as_usize(); if i >= dict_vertex_count { return Err(MltError::DictIndexOutOfBounds(idx, dict_vertex_count)); } acc.push(dict[i * 2]); acc.push(dict[i * 2 + 1]); Ok(acc) }, )?); } Ok(GeometryValues { vector_types, geometry_offsets, part_offsets, ring_offsets, index_buffer, triangles, vertices, }) } } ================================================ FILE: rust/mlt-core/src/decoder/geometry/geotype.rs ================================================ use std::ops::Range; use geo_types::{ Coord, Geometry, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, }; use crate::MltError::{ GeometryIndexOutOfBounds, GeometryOutOfBounds, GeometryVertexOutOfBounds, NoGeometryOffsets, NoPartOffsets, NoRingOffsets, }; use crate::MltResult; use crate::decoder::{GeometryType, GeometryValues}; use crate::utils::AsUsize as _; impl GeometryType { #[must_use] pub fn is_polygon(self) -> bool { matches!(self, Self::Polygon | Self::MultiPolygon) } #[must_use] pub fn is_linestring(self) -> bool { matches!(self, Self::LineString | Self::MultiLineString) } #[must_use] pub fn is_multi(self) -> bool { matches!( self, Self::MultiPoint | Self::MultiLineString | Self::MultiPolygon ) } } impl GeometryValues { /// Geometry types for each feature, in insertion order. #[must_use] pub fn vector_types(&self) -> &[GeometryType] { &self.vector_types } /// Cumulative offsets into `part_offsets` for multi-geometry types. /// `None` when no multi-geometry features are present. #[must_use] pub fn geometry_offsets(&self) -> Option<&[u32]> { self.geometry_offsets.as_deref() } /// Cumulative offsets into `ring_offsets` (or directly into `vertices` /// for `LineString` layers without rings). /// `None` for pure `Point` layers. #[must_use] pub fn part_offsets(&self) -> Option<&[u32]> { self.part_offsets.as_deref() } /// Cumulative offsets into the vertex buffer (counting whole vertices). /// `None` when no ring-level indirection is needed. #[must_use] pub fn ring_offsets(&self) -> Option<&[u32]> { self.ring_offsets.as_deref() } /// Triangle index buffer produced by Earcut tessellation. /// `None` unless the `GeometryValues` was created with [`Self::new_tessellated`]. #[must_use] pub fn index_buffer(&self) -> Option<&[u32]> { self.index_buffer.as_deref() } /// Per-feature triangle counts produced by Earcut tessellation. /// `None` unless the `GeometryValues` was created with [`Self::new_tessellated`]. #[must_use] pub fn triangles(&self) -> Option<&[u32]> { self.triangles.as_deref() } /// Flat vertex buffer: `[x0, y0, x1, y1, …]` in tile coordinates. #[must_use] pub fn vertices(&self) -> Option<&[i32]> { self.vertices.as_deref() } /// Build a `GeoJSON` geometry for a single feature at index `i`. /// Polygon and `MultiPolygon` rings are closed per `GeoJSON` spec /// (MLT omits the closing vertex). pub fn to_geojson(&self, index: usize) -> MltResult> { let verts = self.vertices.as_deref().unwrap_or(&[]); let geoms = self.geometry_offsets.as_deref(); let parts = self.part_offsets.as_deref(); let rings = self.ring_offsets.as_deref(); let off = |s: &[u32], idx: usize, field: &'static str| -> MltResult { s.get(idx) .map(|&v| v.as_usize()) .ok_or(GeometryOutOfBounds { index, field, idx, len: s.len(), }) }; let off_pair = |s: &[u32], idx: usize, field: &'static str| -> MltResult> { Ok(off(s, idx, field)?..off(s, idx + 1, field)?) }; let geom_off = |s: &[u32], i: usize| off(s, i, "geometry_offsets"); let part_off = |s: &[u32], i: usize| off(s, i, "part_offsets"); let ring_off = |s: &[u32], i: usize| off(s, i, "ring_offsets"); let geom_range = |s: &[u32], i: usize| off_pair(s, i, "geometry_offsets"); let part_range = |s: &[u32], i: usize| off_pair(s, i, "part_offsets"); let ring_range = |s: &[u32], i: usize| off_pair(s, i, "ring_offsets"); let vert = |idx: usize| -> MltResult> { verts .get(idx * 2..idx * 2 + 2) .map(|s| Coord { x: s[0], y: s[1] }) .ok_or(GeometryVertexOutOfBounds { index, vertex: idx, count: verts.len() / 2, }) }; let line = |r: Range| -> MltResult> { r.map(&vert).collect() }; let closed_ring = |r: Range| -> MltResult> { let first = r.start; let mut coords: Vec> = r.map(&vert).collect::>()?; coords.push(vert(first)?); Ok(LineString(coords)) }; let poly_from_rings = |part_rng: Range, r: &[u32]| -> MltResult> { let mut rings = part_rng .map(|idx| closed_ring(ring_range(r, idx)?)) .collect::, _>>()? .into_iter(); Ok(Polygon::new( rings.next().unwrap_or_else(|| LineString(vec![])), rings.collect(), )) }; let geom_type = *self .vector_types .get(index) .ok_or(GeometryIndexOutOfBounds(index))?; match geom_type { GeometryType::Point => { // Resolve through hierarchy: geoms? -> parts? -> rings? -> vertex let idx = geoms.map_or(Ok(index), |g| geom_off(g, index))?; let idx = parts.map_or(Ok(idx), |p| part_off(p, idx))?; let idx = rings.map_or(Ok(idx), |r| ring_off(r, idx))?; Ok(Geometry::::Point(Point(vert(idx)?))) } GeometryType::LineString => { let parts = parts.ok_or(NoPartOffsets(index, geom_type))?; // Get part index: use geoms[index] if present, else index directly let part_idx = geoms.map_or(Ok(index), |geom| geom_off(geom, index))?; // With rings: parts[part_idx] gives ring index, use ring_offsets for vertex range // Without rings: use part_offsets directly for vertex range let vert_range = match rings { Some(ring) => ring_range(ring, part_off(parts, part_idx)?)?, None => part_range(parts, part_idx)?, }; line(vert_range).map(Geometry::::LineString) } GeometryType::Polygon => { let parts = parts.ok_or(NoPartOffsets(index, geom_type))?; let rings = rings.ok_or(NoRingOffsets(index, geom_type))?; let idx = geoms .map(|geom| geom_off(geom, index)) .transpose()? .unwrap_or(index); poly_from_rings(part_range(parts, idx)?, rings).map(Geometry::::Polygon) } GeometryType::MultiPoint => { let geoms = geoms.ok_or(NoGeometryOffsets(index, geom_type))?; let geom_rng = geom_range(geoms, index)?; // Resolve vertex index through parts?->rings? hierarchy // When ring_offsets exist (polygon geometry present), geometry_offsets indexes // into part_offsets which indexes into ring_offsets for vertex indices. // When only part_offsets exist, geometry_offsets indexes into part_offsets // which gives direct vertex indices. // When neither exist, geometry_offsets gives direct vertex indices. let coords: Result, _> = match (parts, rings) { (Some(parts), Some(rings)) => geom_rng .map(|idx| vert(ring_off(rings, part_off(parts, idx)?)?)) .collect(), (Some(part), None) => geom_rng.map(|idx| vert(part_off(part, idx)?)).collect(), (None, _) => geom_rng.map(&vert).collect(), }; Ok(Geometry::::MultiPoint(MultiPoint( coords?.into_iter().map(Point).collect(), ))) } GeometryType::MultiLineString => { let geoms = geoms.ok_or(NoGeometryOffsets(index, geom_type))?; let parts = parts.ok_or(NoPartOffsets(index, geom_type))?; let geom_rng = geom_range(geoms, index)?; // geometry_offsets indexes into part_offsets for each linestring. // When ring_offsets exist (polygon geometry present), part_offsets indexes // into ring_offsets for vertex ranges. Otherwise, part_offsets directly // gives vertex ranges. let lines: Result, _> = match rings { Some(ring) => geom_rng .map(|idx| line(ring_range(ring, part_off(parts, idx)?)?)) .collect(), None => geom_rng.map(|idx| line(part_range(parts, idx)?)).collect(), }; Ok(Geometry::::MultiLineString(MultiLineString(lines?))) } GeometryType::MultiPolygon => { let geoms = geoms.ok_or(NoGeometryOffsets(index, geom_type))?; let parts = parts.ok_or(NoPartOffsets(index, geom_type))?; let rings = rings.ok_or(NoRingOffsets(index, geom_type))?; let polys: Vec<_> = geom_range(geoms, index)? .map(|idx| poly_from_rings(part_range(parts, idx)?, rings)) .collect::>()?; Ok(Geometry::::MultiPolygon(MultiPolygon(polys))) } } } } ================================================ FILE: rust/mlt-core/src/decoder/geometry/mod.rs ================================================ pub(crate) mod decode; mod geotype; mod model; pub use model::*; ================================================ FILE: rust/mlt-core/src/decoder/geometry/model.rs ================================================ use derive_debug::Dbg; use num_enum::TryFromPrimitive; use serde::{Deserialize, Serialize}; use crate::decoder::RawStream; use crate::utils::formatter::{opt_vec_seq, vec_seq}; use crate::{DecodeState, Lazy}; /// Geometry column representation, parameterized by decode state. /// /// - `Geometry<'a>` / `Geometry<'a, Lazy>` — either raw bytes or decoded, in an [`crate::LazyParsed`] enum. /// - `Geometry<'a, Parsed>` — decoded [`GeometryValues`] directly (no enum wrapper). pub type Geometry<'a, S = Lazy> = ::LazyOrParsed, GeometryValues>; /// Raw geometry data as read directly from the tile (borrows from input bytes) #[derive(Debug, PartialEq, Clone)] pub struct RawGeometry<'a> { pub(crate) meta: RawStream<'a>, pub(crate) items: Vec>, } /// Parsed (decoded) geometry data #[derive(Clone, Dbg, Default, PartialEq, Eq)] pub struct GeometryValues { #[dbg(formatter = "vec_seq")] pub(crate) vector_types: Vec, #[dbg(formatter = "opt_vec_seq")] pub(crate) geometry_offsets: Option>, #[dbg(formatter = "opt_vec_seq")] pub(crate) part_offsets: Option>, #[dbg(formatter = "opt_vec_seq")] pub(crate) ring_offsets: Option>, #[dbg(formatter = "opt_vec_seq")] pub(crate) index_buffer: Option>, #[dbg(formatter = "opt_vec_seq")] pub(crate) triangles: Option>, #[dbg(formatter = "opt_vec_seq")] pub(crate) vertices: Option>, } /// Types of geometries supported in MLT #[derive( Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Hash, Ord, TryFromPrimitive, strum::Display, strum::IntoStaticStr, Serialize, Deserialize, )] #[repr(u8)] #[cfg_attr(test, derive(proptest_derive::Arbitrary))] pub enum GeometryType { /* ATTENTION: Do not modify the order of this enum - it is being used in geometry decoding */ Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, } ================================================ FILE: rust/mlt-core/src/decoder/id/decode.rs ================================================ use crate::decoder::{ParsedId, RawId, RawIdValue}; use crate::utils::decode_presence; use crate::{Decode, Decoder, MltResult}; impl<'a> Decode> for RawId<'a> { /// Decode into a [`ParsedId`], charging `dec` before each allocation. fn decode(self, dec: &mut Decoder) -> MltResult> { let RawId { presence, value } = self; let values: Vec = match value { RawIdValue::Id32(stream) => { // FIXME: ParsedId should be an enum of u32 or u64 to avoid extra allocation let ids = stream.decode_u32s(dec)?; dec.consume_items::(ids.len())?; ids.into_iter().map(u64::from).collect() } RawIdValue::Id64(stream) => stream.decode_u64s(dec)?, }; Ok(ParsedId(decode_presence(presence, values, dec)?)) } } ================================================ FILE: rust/mlt-core/src/decoder/id/mod.rs ================================================ mod decode; mod model; pub use model::*; ================================================ FILE: rust/mlt-core/src/decoder/id/model.rs ================================================ use std::ops::Deref; use crate::decoder::{RawPresence, RawStream}; use crate::utils::Presence; use crate::utils::analyze::AnalyzeViaDeref; use crate::{DecodeState, Lazy}; /// ID column representation, parameterized by decode state. /// /// - `Id<'a>` / `Id<'a, Lazy>` — either raw bytes or decoded, in a [`crate::LazyParsed`] enum. /// - `Id<'a, Parsed>` — decoded [`ParsedId`] directly (no enum wrapper). pub type Id<'a, S = Lazy> = ::LazyOrParsed, ParsedId<'a>>; /// Unparsed ID data as read directly from the tile (borrows from input bytes) #[derive(Debug, PartialEq, Clone)] pub struct RawId<'a> { pub(crate) presence: RawPresence<'a>, pub(crate) value: RawIdValue<'a>, } /// A sequence of raw ID values, either 32-bit or 64-bit unsigned integers #[derive(Debug, PartialEq, Clone)] pub enum RawIdValue<'a> { Id32(RawStream<'a>), Id64(RawStream<'a>), } /// Decoded ID column. /// /// A transparent type over [`Presence<'a, u64>`]. All feature-access methods /// (`get`, `feature_count`, `dense_values`, `materialize`, `is_present`) are /// available via auto-deref. /// /// The lifetime `'a` allows zero-copy decoding when the inner bitvector borrows /// from the source bytes. When the presence stream is RLE-decompressed, the inner /// `Cow` becomes owned and no longer borrows from the input. // TODO: consider converting ParsedId to an enum with u32 vs u64 for performance #[derive(Clone, Debug, PartialEq)] pub struct ParsedId<'a>(pub(crate) Presence<'a, u64>); impl<'a> Deref for ParsedId<'a> { type Target = Presence<'a, u64>; #[inline] fn deref(&self) -> &Self::Target { &self.0 } } impl AnalyzeViaDeref for ParsedId<'_> {} ================================================ FILE: rust/mlt-core/src/decoder/iterators.rs ================================================ //! Zero-copy per-feature view into a fully-decoded [`Layer01`]. //! //! [`ParsedLayer01::iter_features`] yields one [`FeatureRef`] per feature via //! [`LendingIterator`]. [`FeatureRef::iter_properties`] exposes per-feature //! property values as flat [`ColumnRef`] items; `SharedDict` columns are //! transparently expanded and null values are skipped. //! //! # Iterator model //! //! Feature iteration uses [`LendingIterator`] rather than [`std::iter::Iterator`]. //! This allows the iterator to reuse an internal buffer across steps — the //! [`FeatureRef`] borrows its property values from that buffer — eliminating a //! per-feature `Vec` allocation. //! //! The consequence is that each [`FeatureRef`] must be dropped before calling //! [`LendingIterator::next`] again, so standard adapters like `.map()` and //! `.collect()` are **not** available directly. Use a `while let` loop instead: use std::fmt; use geo_types::Geometry; use crate::decoder::{Layer01, ParsedLayer01, ParsedProperty, ParsedScalar, RawProperty}; use crate::{Lazy, LazyParsed, MltResult, Parsed}; /// A minimal lending (streaming) iterator trait. /// /// Unlike [`std::iter::Iterator`], the item type may borrow from the iterator /// itself, enabling zero-allocation iteration where the inner buffer is reused /// across steps. /// /// Use a `while let` loop to drive the iterator: /// ```ignore /// let mut iter = layer.iter_features(); /// while let Some(feat) = iter.next() { /// let feat = feat?; /// /* use feat here — it borrows from iter */ /// } /// ``` pub trait LendingIterator { /// The type of each element, which may borrow from `self`. type Item<'this> where Self: 'this; /// Advance the iterator, returning the next element or `None` when exhausted. fn next(&mut self) -> Option>; } impl<'a> Layer01<'a, Lazy> { /// Iterate over the property column names of this layer, in order. /// /// Regular columns yield one [`PropName`]; `SharedDict` columns yield one name per /// sub-item. Names are available even before any column data has been decoded. /// /// Pair with [`FeatureRef::iter_all_properties`] to associate per-feature /// values with their column names. pub fn iterate_prop_names(&self) -> impl Iterator> + '_ { let props = &self.properties; let mut col_idx = 0; let mut dict_idx = 0; std::iter::from_fn(move || { loop { let idx = col_idx; col_idx += 1; let name = match props.get(idx)? { LazyParsed::Raw(r) => raw_col_name(r, &mut dict_idx), LazyParsed::Parsed(p) => parsed_col_name(p, &mut dict_idx), LazyParsed::ParsingFailed => None, }; if dict_idx != 0 { col_idx -= 1; } if let Some(n) = name { return Some(n); } } }) } } impl<'a> ParsedLayer01<'a> { /// Iterate over all features in this fully-decoded layer via a [`LendingIterator`]. /// /// Yields one `MltResult<`[`FeatureRef`]`>` per feature. Geometry decoding can /// fail, hence the `Result` wrapper. /// /// ```text /// let mut iter = parsed.iter_features(); /// while let Some(feat) = iter.next() { /// let feat = feat?; /// for col in feat.iter_properties() { /// // or use iter_all_properties() to include Nones /// } /// } /// ``` /// /// All inner iterators — [`FeatureRef::iter_properties`], /// [`FeatureRef::iter_all_properties`], and the name iterators — implement the /// standard [`std::iter::Iterator`] trait and compose normally. #[must_use] pub fn iter_features(&self) -> Layer01FeatureIter<'_, 'a> { Layer01FeatureIter::new(self) } /// Iterate over the property column names of this layer, in order. /// See [`Layer01::iterate_prop_names`] for details. pub fn iterate_prop_names(&self) -> impl Iterator> + '_ { Layer01PropNamesIter::new(&self.properties) } } /// A zero-allocation two-part property name yielded by [`FeatureRef::iter_properties`]. /// /// The two parts concatenate on [`Display`](fmt::Display) as `"{}{}"`: /// - For regular columns: `(column_name, "")` — zero allocation, second part always empty. /// - For `SharedDict` sub-items: `(prefix, suffix)` — both borrow directly from layer data. /// /// Structural [`PartialEq`] compares both parts independently. Use [`PartialEq`] or /// [`PartialEq<&str>`] (also implemented) to compare against a plain `&str` as if the two /// parts were concatenated. #[derive(Debug, Clone, Copy)] // WARN: do not auto-derive PartialEq,Eq,Hash as it won't be correct pub struct PropName<'a>(&'a str, &'a str); impl fmt::Display for PropName<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.0)?; f.write_str(self.1) } } impl PartialEq> for PropName<'_> { fn eq(&self, other: &PropName<'_>) -> bool { // Compare the concatenated strings byte-by-byte without allocating. let (a0, a1) = (self.0.as_bytes(), self.1.as_bytes()); let a = a0.iter().chain(a1); let (b0, b1) = (other.0.as_bytes(), other.1.as_bytes()); let b = b0.iter().chain(b1); let combined_len_eq = a0.len() + a1.len() == b0.len() + b1.len(); combined_len_eq && a.eq(b) } } impl PartialEq for PropName<'_> { /// Returns `true` if `other == self.0 + self.1`. fn eq(&self, other: &str) -> bool { other.strip_prefix(self.0) == Some(self.1) } } impl PartialEq> for str { fn eq(&self, other: &PropName<'_>) -> bool { other == self } } impl PartialEq<&str> for PropName<'_> { fn eq(&self, other: &&str) -> bool { self == *other } } impl PartialEq> for &str { fn eq(&self, other: &PropName<'_>) -> bool { other == *self } } /// A borrowed, non-null per-feature property value. /// /// Nullability is lifted to [`ColumnRef`]: only non-null values appear in /// [`FeatureRef::iter_properties`]. #[derive(Debug, Clone, Copy, PartialEq)] pub enum PropValueRef<'a> { Bool(bool), I8(i8), U8(u8), I32(i32), U32(u32), I64(i64), U64(u64), F32(f32), F64(f64), Str(&'a str), } macro_rules! impl_from_for_prop_value_ref { ($($ty:ty => $variant:ident),+ $(,)?) => { $(impl From<$ty> for PropValueRef<'_> { fn from(v: $ty) -> Self { Self::$variant(v) } })+ }; } impl_from_for_prop_value_ref!( bool => Bool, i8 => I8, u8 => U8, i32 => I32, u32 => U32, i64 => I64, u64 => U64, f32 => F32, f64 => F64, ); /// A single non-null property value for one feature, yielded by [`FeatureRef::iter_properties`]. /// /// `name` is a [`PropName`] that displays as `"{prefix}{suffix}"`. /// All borrows are zero-copy from the layer data. #[derive(Debug, Clone, Copy, PartialEq)] pub struct ColumnRef<'a> { pub name: PropName<'a>, pub value: PropValueRef<'a>, } /// A single map feature returned by [`ParsedLayer01::iter_features`]. /// /// Borrows `values` from the outer [`Layer01FeatureIter`] buffer — it must be /// dropped before calling [`LendingIterator::next`] again. #[derive(Debug)] pub struct FeatureRef<'feat, 'layer: 'feat> { /// Optional feature ID. pub id: Option, /// Geometry in [`Geometry`] form (owned, decoded on demand by the iterator). pub geometry: Geometry, /// Borrowed slice of column descriptors from the layer; used to yield column names. columns: &'layer [ParsedProperty<'layer>], /// Per-feature values in column order, one per slot (scalar, string, or `SharedDict` /// sub-item). Borrowed from the iterator's reused buffer — no allocation per feature. values: &'feat [Option>], } impl<'feat, 'layer: 'feat> FeatureRef<'feat, 'layer> { /// Iterate over every property slot for this feature, **values only**, in column order. /// /// Yields `Option`: /// - `Some(value)` — the slot contains a non-null value. /// - `None` — the slot is null / absent. /// /// Use [`Layer01::iterate_prop_names`] to pair values with their column names. pub fn iter_all_properties(&self) -> impl Iterator>> + '_ { self.values.iter().copied() } /// Iterate over all non-null properties for this feature. /// /// `SharedDict` columns are transparently expanded into one [`ColumnRef`] per sub-item. /// Null / absent values are skipped entirely. The iterator is infallible. pub fn iter_properties(&self) -> impl Iterator> + '_ { Layer01PropNamesIter::new(self.columns) .zip(self.values.iter().copied()) .filter_map(|(name, opt_val)| opt_val.map(|value| ColumnRef { name, value })) } /// Look up a property by name, returning its value if present and non-null. /// /// For `SharedDict` columns the expected name is `"{prefix}{suffix}"`, matching /// the key used by [`iter_properties`](Self::iter_properties). #[must_use] pub fn get_property(&self, name: &str) -> Option> { self.iter_properties() .find(|col| col.name == name) .map(|col| col.value) } } // ── Column name helpers ─────────────────────────────────────────────────────── /// Iterates the property column names of a fully-decoded [`ParsedLayer01`]. /// /// Regular columns yield one [`PropName`]; `SharedDict` columns yield one name per /// sub-item (`(prefix, suffix)`). pub(crate) struct Layer01PropNamesIter<'a, 'p> { props: &'a [ParsedProperty<'p>], col_idx: usize, dict_idx: usize, } impl<'a, 'p> Layer01PropNamesIter<'a, 'p> { pub(crate) fn new(props: &'a [ParsedProperty<'p>]) -> Self { Self { props, col_idx: 0, dict_idx: 0, } } } impl<'a> Iterator for Layer01PropNamesIter<'_, 'a> { type Item = PropName<'a>; fn next(&mut self) -> Option> { loop { let col_idx = self.col_idx; self.col_idx += 1; let name = parsed_col_name(self.props.get(col_idx)?, &mut self.dict_idx); if self.dict_idx != 0 { self.col_idx -= 1; // SharedDict not yet exhausted: revisit this column } if let Some(n) = name { return Some(n); } } } } /// Yield the next [`PropName`] from a [`ParsedProperty`] column. #[inline] fn parsed_col_name<'p>(prop: &ParsedProperty<'p>, dict_idx: &mut usize) -> Option> { use ParsedProperty as P; match prop { P::Bool(s) => Some(PropName(s.name, "")), P::I8(s) => Some(PropName(s.name, "")), P::U8(s) => Some(PropName(s.name, "")), P::I32(s) => Some(PropName(s.name, "")), P::U32(s) => Some(PropName(s.name, "")), P::I64(s) => Some(PropName(s.name, "")), P::U64(s) => Some(PropName(s.name, "")), P::F32(s) => Some(PropName(s.name, "")), P::F64(s) => Some(PropName(s.name, "")), P::Str(s) => Some(PropName(s.name, "")), P::SharedDict(sd) => { if *dict_idx < sd.items.len() { let idx = *dict_idx; *dict_idx += 1; Some(PropName(sd.prefix, sd.items[idx].suffix)) } else { *dict_idx = 0; None } } } } /// Yield the next [`PropName`] from a [`RawProperty`] column. See [`parsed_col_name`]. #[inline] fn raw_col_name<'p>(prop: &RawProperty<'p>, dict_idx: &mut usize) -> Option> { use RawProperty as P; match prop { P::Bool(s) | P::I8(s) | P::U8(s) | P::I32(s) | P::U32(s) | P::I64(s) | P::U64(s) | P::F32(s) | P::F64(s) => Some(PropName(s.name, "")), P::Str(s) => Some(PropName(s.name, "")), P::SharedDict(sd) => { if *dict_idx < sd.children.len() { let idx = *dict_idx; *dict_idx += 1; Some(PropName(sd.name, sd.children[idx].name)) } else { *dict_idx = 0; None } } } } /// A boxed per-column-slot value iterator yielding one `Option<`[`PropValueRef`]`>` per feature. type ColValIter<'l> = Box>> + 'l>; /// Build one [`ColValIter`] per property column "slot" from a decoded column slice. /// /// - Scalar and string columns contribute one slot each. /// - `SharedDict` columns contribute one slot per sub-item. fn build_col_iters<'p>(columns: &'p [ParsedProperty<'p>]) -> Vec> { use ParsedProperty as PP; let mut iters: Vec> = Vec::new(); for col in columns { match col { PP::Bool(s) => iters.push(scalar_col_iter(s)), PP::I8(s) => iters.push(scalar_col_iter(s)), PP::U8(s) => iters.push(scalar_col_iter(s)), PP::I32(s) => iters.push(scalar_col_iter(s)), PP::U32(s) => iters.push(scalar_col_iter(s)), PP::I64(s) => iters.push(scalar_col_iter(s)), PP::U64(s) => iters.push(scalar_col_iter(s)), PP::F32(s) => iters.push(scalar_col_iter(s)), PP::F64(s) => iters.push(scalar_col_iter(s)), PP::Str(strings) => { let data: &'p str = strings.data.as_ref(); let lengths: &'p [i32] = &strings.lengths; let mut curr_end: u32 = 0; let mut feat_idx = 0usize; iters.push(Box::new(std::iter::from_fn(move || { let &end_i32 = lengths.get(feat_idx)?; feat_idx += 1; if end_i32 >= 0 { let start = curr_end as usize; curr_end = end_i32.cast_unsigned(); Some(data.get(start..curr_end as usize).map(PropValueRef::Str)) } else { // Null slot: curr_end unchanged (null encodes the current byte offset). Some(None) } }))); } PP::SharedDict(dict) => { for item in &dict.items { let dict_ref: &'p _ = dict; let item_ref: &'p _ = item; let mut feat_idx = 0usize; iters.push(Box::new(std::iter::from_fn(move || { if feat_idx >= item_ref.ranges.len() { return None; } let idx = feat_idx; feat_idx += 1; Some(item_ref.get(dict_ref, idx).map(PropValueRef::Str)) }))); } } } } iters } /// Build a boxed value iterator for a single scalar property column. fn scalar_col_iter<'p, T>(scalar: &'p ParsedScalar<'p, T>) -> ColValIter<'p> where T: Copy + PartialEq, PropValueRef<'p>: From, { Box::new(scalar.iter_optional().map(|o| o.map(PropValueRef::from))) } /// Iterator over the features of a fully-decoded [`Layer01`]. /// /// Returned by [`ParsedLayer01::iter_features`]. Implements [`LendingIterator`]: /// advance with `while let Some(feat) = iter.next()`. /// /// Holds one O(1)-per-step cursor per property column slot. On each step the /// per-column cursors are advanced and their results written into a reused /// `values_buf` — yielding a [`FeatureRef`] that borrows that buffer with no /// per-feature heap allocation. pub struct Layer01FeatureIter<'layer, 'data: 'layer> { layer: &'layer Layer01<'data, Parsed>, index: usize, feature_count: usize, /// ID iterator, `None` when the layer has no ID column. id_iter: Option>, /// One boxed value iterator per column slot (scalar, string, or `SharedDict` sub-item). col_iters: Vec>, /// Reused buffer: filled on each `next()` call, borrowed by the yielded [`FeatureRef`]. values_buf: Vec>>, } impl<'layer, 'data: 'layer> Layer01FeatureIter<'layer, 'data> { fn new(layer: &'layer Layer01<'data, Parsed>) -> Self { let col_iters = build_col_iters(&layer.properties); let cap = col_iters.len(); Self { layer, index: 0, feature_count: layer.feature_count(), id_iter: layer.id.as_ref().map(|id| id.iter_optional()), col_iters, values_buf: Vec::with_capacity(cap), } } /// Number of features not yet yielded. #[must_use] pub fn len(&self) -> usize { self.feature_count - self.index } /// Returns `true` if all features have been yielded. #[must_use] pub fn is_empty(&self) -> bool { self.index >= self.feature_count } } impl<'layer> LendingIterator for Layer01FeatureIter<'layer, '_> { type Item<'this> = MltResult> where Self: 'this; fn next(&mut self) -> Option> { let index = self.index; if index >= self.feature_count { return None; } self.index += 1; // Advance all per-feature cursors unconditionally, even if geometry decode fails, // so that IDs and property values remain aligned with geometry indices. let id = self.id_iter.as_mut().and_then(Iterator::next).flatten(); self.values_buf.clear(); self.values_buf .extend(self.col_iters.iter_mut().map(|it| it.next().flatten())); Some( self.layer .geometry .to_geojson(index) .map(|geometry| FeatureRef { id, geometry, columns: &self.layer.properties, values: &self.values_buf, }), ) } } #[cfg(test)] mod tests { use geo_types::Point; use serde_json::Value; use super::*; use crate::Layer; use crate::decoder::GeometryValues; use crate::encoder::model::StagedLayer; use crate::encoder::{Codecs, Encoder, Presence, StagedId, StagedProperty, StagedSharedDict}; use crate::test_helpers::{dec, parser}; fn layer_buf(staged: StagedLayer) -> Vec { staged .encode_into(Encoder::default(), &mut Codecs::default()) .unwrap() .into_layer_bytes() .unwrap() } fn three_points() -> GeometryValues { let mut g = GeometryValues::default(); g.push_geom(&Geometry::::Point(Point::new(1, 2))); g.push_geom(&Geometry::::Point(Point::new(3, 4))); g.push_geom(&Geometry::::Point(Point::new(5, 6))); g } fn empty_layer(name: &str) -> StagedLayer { StagedLayer { name: name.to_string(), extent: 4096, id: StagedId::None, geometry: GeometryValues::default(), properties: vec![], } } #[test] fn prop_name_display_concatenates_parts() { assert_eq!(PropName("addr:", "city").to_string(), "addr:city"); assert_eq!(PropName("name", "").to_string(), "name"); assert_eq!(PropName("", "").to_string(), ""); } #[test] fn prop_name_eq_str_matches_concatenation() { assert_eq!(PropName("addr:", "city"), "addr:city"); assert_eq!("addr:city", PropName("addr:", "city")); assert_ne!(PropName("addr:", "city"), "addr:"); assert_ne!(PropName("addr:", "city"), "city"); assert_eq!(PropName("name", ""), "name"); } #[test] fn prop_name_structural_eq_is_part_wise() { assert_eq!(PropName("a", "b"), PropName("a", "b")); assert_eq!(PropName("ab", ""), PropName("a", "b")); } #[test] fn prop_name_eq_prop_name_semantic_equality() { assert_eq!(PropName("ab", ""), PropName("a", "b")); assert_eq!(PropName("", "ab"), PropName("a", "b")); assert_eq!(PropName("abc", "def"), PropName("ab", "cdef")); assert_eq!(PropName("a", "bcdef"), PropName("abcde", "f")); assert_ne!(PropName("a", "b"), PropName("a", "c")); assert_ne!(PropName("a", "b"), PropName("ab", "c")); assert_ne!(PropName("abc", ""), PropName("ab", "")); } #[test] fn prop_value_ref_scalars_convert_to_json() { assert_eq!(Value::from(PropValueRef::Bool(true)), Value::Bool(true)); assert_eq!(Value::from(PropValueRef::Bool(false)), Value::Bool(false)); assert_eq!(Value::from(PropValueRef::I8(-1)), Value::from(-1_i8)); assert_eq!(Value::from(PropValueRef::U8(255)), Value::from(255_u8)); assert_eq!( Value::from(PropValueRef::I32(-1000)), Value::from(-1000_i32) ); assert_eq!(Value::from(PropValueRef::U32(1000)), Value::from(1000_u32)); assert_eq!( Value::from(PropValueRef::I64(i64::MIN)), Value::from(i64::MIN) ); assert_eq!( Value::from(PropValueRef::U64(u64::MAX)), Value::from(u64::MAX) ); assert_eq!( Value::from(PropValueRef::Str("hello")), Value::String("hello".into()) ); } #[test] fn prop_value_ref_float_finite_is_number() { assert!(matches!( Value::from(PropValueRef::F32(1.5)), Value::Number(_) )); assert!(matches!( Value::from(PropValueRef::F64(2.5)), Value::Number(_) )); } #[test] fn prop_value_ref_float_non_finite_becomes_string_sentinel() { assert_eq!( Value::from(PropValueRef::F32(f32::NAN)), Value::String("f32::NAN".into()) ); assert_eq!( Value::from(PropValueRef::F32(f32::INFINITY)), Value::String("f32::INFINITY".into()) ); assert_eq!( Value::from(PropValueRef::F64(f64::NAN)), Value::String("f64::NAN".into()) ); assert_eq!( Value::from(PropValueRef::F64(f64::NEG_INFINITY)), Value::String("f64::NEG_INFINITY".into()) ); } #[test] fn empty_layer_yields_no_features() { let buf = layer_buf(empty_layer("empty")); let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap(); let Layer::Tag01(lazy) = layer else { panic!("expected Tag01") }; let parsed = lazy.decode_all(&mut dec()).unwrap(); let iter = parsed.iter_features(); assert_eq!(iter.len(), 0); assert!(iter.is_empty()); assert_eq!(parsed.iter_features().len(), 0); } #[test] fn len_decreases_with_each_next() { let buf = layer_buf(StagedLayer { name: "test".into(), extent: 4096, id: StagedId::None, geometry: three_points(), properties: vec![], }); let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap(); let Layer::Tag01(lazy) = layer else { panic!() }; let parsed = lazy.decode_all(&mut dec()).unwrap(); let mut iter = parsed.iter_features(); assert_eq!(iter.len(), 3); iter.next().unwrap().unwrap(); assert_eq!(iter.len(), 2); iter.next().unwrap().unwrap(); assert_eq!(iter.len(), 1); iter.next().unwrap().unwrap(); assert_eq!(iter.len(), 0); assert!(iter.is_empty()); assert!(iter.next().is_none()); } #[test] fn feature_ids_are_preserved() { let buf = layer_buf(StagedLayer { name: "test".into(), extent: 4096, id: StagedId::from_optional(vec![Some(100), None, Some(200)]), geometry: three_points(), properties: vec![], }); let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap(); let Layer::Tag01(lazy) = layer else { panic!() }; let parsed = lazy.decode_all(&mut dec()).unwrap(); let mut ids = Vec::new(); let mut iter = parsed.iter_features(); while let Some(r) = iter.next() { ids.push(r.unwrap().id); } assert_eq!(ids, [Some(100), None, Some(200)]); } #[test] fn geometry_values_match_input() { let buf = layer_buf(StagedLayer { name: "test".into(), extent: 4096, id: StagedId::None, geometry: three_points(), properties: vec![], }); let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap(); let Layer::Tag01(lazy) = layer else { panic!() }; let parsed = lazy.decode_all(&mut dec()).unwrap(); let mut geoms = Vec::new(); let mut iter = parsed.iter_features(); while let Some(r) = iter.next() { geoms.push(r.unwrap().geometry); } assert_eq!(geoms[0], Geometry::::Point(Point::new(1, 2))); assert_eq!(geoms[1], Geometry::::Point(Point::new(3, 4))); assert_eq!(geoms[2], Geometry::::Point(Point::new(5, 6))); } #[test] fn null_scalar_values_are_skipped() { let buf = layer_buf(StagedLayer { name: "test".into(), extent: 4096, id: StagedId::None, geometry: three_points(), properties: vec![StagedProperty::opt_u32("n", vec![Some(1), None, Some(3)])], }); let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap(); let Layer::Tag01(lazy) = layer else { panic!() }; let parsed = lazy.decode_all(&mut dec()).unwrap(); let mut iter = parsed.iter_features(); { let feat = iter.next().unwrap().unwrap(); let cols: Vec<_> = feat.iter_properties().collect(); assert_eq!(cols.len(), 1); assert_eq!(cols[0].name, PropName("n", "")); assert_eq!(cols[0].name, "n"); assert_eq!(cols[0].value, PropValueRef::U32(1)); let all: Vec<_> = feat.iter_all_properties().collect(); assert_eq!(all, [Some(PropValueRef::U32(1))]); } { let feat = iter.next().unwrap().unwrap(); assert!(feat.iter_properties().next().is_none()); let all: Vec<_> = feat.iter_all_properties().collect(); assert_eq!(all, [None]); } { let feat = iter.next().unwrap().unwrap(); assert_eq!(feat.get_property("n"), Some(PropValueRef::U32(3))); let all: Vec<_> = feat.iter_all_properties().collect(); assert_eq!(all, [Some(PropValueRef::U32(3))]); } let names: Vec<_> = parsed.iterate_prop_names().map(|n| n.to_string()).collect(); assert_eq!(names, ["n"]); } #[test] fn null_string_values_are_skipped() { let buf = layer_buf(StagedLayer { name: "test".into(), extent: 4096, id: StagedId::None, geometry: three_points(), properties: vec![StagedProperty::opt_str( "label", vec![Some("foo"), None, Some("bar")], )], }); let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap(); let Layer::Tag01(lazy) = layer else { panic!() }; let parsed = lazy.decode_all(&mut dec()).unwrap(); let mut iter = parsed.iter_features(); { let feat = iter.next().unwrap().unwrap(); assert_eq!(feat.get_property("label"), Some(PropValueRef::Str("foo"))); } { let feat = iter.next().unwrap().unwrap(); assert_eq!(feat.get_property("label"), None); } { let feat = iter.next().unwrap().unwrap(); assert_eq!(feat.get_property("label"), Some(PropValueRef::Str("bar"))); } } #[test] fn multiple_columns_independently_nullable() { let buf = layer_buf(StagedLayer { name: "test".into(), extent: 4096, id: StagedId::None, geometry: three_points(), properties: vec![ StagedProperty::opt_bool("flag", vec![Some(true), Some(false), None]), StagedProperty::opt_i32("score", vec![None, Some(-5), Some(7)]), ], }); let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap(); let Layer::Tag01(lazy) = layer else { panic!() }; let parsed = lazy.decode_all(&mut dec()).unwrap(); let mut iter = parsed.iter_features(); // feat 0: flag=true, score=null → 1 property { let feat = iter.next().unwrap().unwrap(); assert_eq!(feat.iter_properties().count(), 1); assert_eq!(feat.get_property("flag"), Some(PropValueRef::Bool(true))); assert_eq!(feat.get_property("score"), None); } // feat 1: flag=false, score=-5 → 2 properties { let feat = iter.next().unwrap().unwrap(); assert_eq!(feat.iter_properties().count(), 2); assert_eq!(feat.get_property("flag"), Some(PropValueRef::Bool(false))); assert_eq!(feat.get_property("score"), Some(PropValueRef::I32(-5))); } // feat 2: flag=null, score=7 → 1 property { let feat = iter.next().unwrap().unwrap(); assert_eq!(feat.iter_properties().count(), 1); assert_eq!(feat.get_property("flag"), None); assert_eq!(feat.get_property("score"), Some(PropValueRef::I32(7))); } } #[test] fn geometry_error_does_not_misalign_ids() { use crate::decoder::GeometryType; let buf = layer_buf(StagedLayer { name: "test".into(), extent: 4096, id: StagedId::from_optional(vec![Some(10), Some(20), Some(30)]), geometry: three_points(), properties: vec![], }); let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap(); let Layer::Tag01(lazy) = layer else { panic!() }; let mut parsed = lazy.decode_all(&mut dec()).unwrap(); // Corrupt feature 1's geometry type: Point → LineString. // A LineString requires part_offsets, which are absent here, so // to_geojson(1) will return Err(NoPartOffsets). parsed.geometry.vector_types[1] = GeometryType::LineString; let mut iter = parsed.iter_features(); // Feature 0: valid Point, id = Some(10) let feat0 = iter.next().unwrap().unwrap(); assert_eq!(feat0.id, Some(10)); // Feature 1: geometry error — iterator still advances ID cursor assert!(iter.next().unwrap().is_err()); // Feature 2: valid Point, id must be Some(30), not Some(20) let feat2 = iter.next().unwrap().unwrap(); assert_eq!( feat2.id, Some(30), "id cursor was not advanced on geometry error" ); assert!(iter.next().is_none()); } #[test] fn get_property_absent_column_returns_none() { let buf = layer_buf(StagedLayer { name: "test".into(), extent: 4096, id: StagedId::None, geometry: three_points(), properties: vec![StagedProperty::u32("x", vec![1, 2, 3])], }); let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap(); let Layer::Tag01(lazy) = layer else { panic!() }; let parsed = lazy.decode_all(&mut dec()).unwrap(); let mut iter = parsed.iter_features(); let feat = iter.next().unwrap().unwrap(); assert_eq!(feat.get_property("no_such_column"), None); } #[test] fn shared_dict_columns_are_expanded() { let shared_dict = StagedSharedDict::new( "addr:", [ ( "city", vec![Some("Paris"), Some("Rome"), None], Presence::Mixed, ), ( "zip", vec![Some("75001"), None, Some("00100")], Presence::Mixed, ), ], ) .unwrap(); let buf = layer_buf(StagedLayer { name: "test".into(), extent: 4096, id: StagedId::None, geometry: three_points(), properties: vec![StagedProperty::SharedDict(shared_dict)], }); let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap(); let Layer::Tag01(lazy) = layer else { panic!() }; let parsed = lazy.decode_all(&mut dec()).unwrap(); let mut iter = parsed.iter_features(); // feat 0: city=Paris, zip=75001 { let feat = iter.next().unwrap().unwrap(); assert_eq!( feat.get_property("addr:city"), Some(PropValueRef::Str("Paris")) ); assert_eq!( feat.get_property("addr:zip"), Some(PropValueRef::Str("75001")) ); assert_eq!(feat.iter_properties().count(), 2); } // feat 1: city=Rome, zip=null { let feat = iter.next().unwrap().unwrap(); assert_eq!( feat.get_property("addr:city"), Some(PropValueRef::Str("Rome")) ); assert_eq!(feat.get_property("addr:zip"), None); assert_eq!(feat.iter_properties().count(), 1); // iter_all_properties: values only (no names); SharedDict expands to two slots let all: Vec<_> = feat.iter_all_properties().collect(); assert_eq!(all, [Some(PropValueRef::Str("Rome")), None]); } // feat 2: city=null, zip=00100 { let feat = iter.next().unwrap().unwrap(); assert_eq!(feat.get_property("addr:city"), None); assert_eq!( feat.get_property("addr:zip"), Some(PropValueRef::Str("00100")) ); } let names: Vec<_> = parsed.iterate_prop_names().map(|n| n.to_string()).collect(); assert_eq!(names, ["addr:city", "addr:zip"]); } } ================================================ FILE: rust/mlt-core/src/decoder/layer.rs ================================================ use crate::codecs::varint::parse_varint; use crate::decoder::{Layer01, Unknown}; use crate::utils::{parse_u8, take}; use crate::{DecodeState, Decoder, Layer, MltError, MltRefResult, MltResult, ParsedLayer, Parser}; impl<'a, S: DecodeState> Layer<'a, S> { /// Returns the inner `Layer01` if this is a Tag01 layer, or `None` otherwise. #[must_use] pub fn as_layer01(&self) -> Option<&Layer01<'a, S>> { match self { Self::Tag01(l) => Some(l), Self::Unknown(_) => None, } } /// Consumes this layer and returns the inner `Layer01`, or `None` if it is not a Tag01 layer. #[must_use] pub fn into_layer01(self) -> Option> { match self { Self::Tag01(l) => Some(l), Self::Unknown(_) => None, } } } impl<'a> Layer<'a> { /// Parse a single tuple that consists of `size (varint)`, `tag (varint)`, and `value (bytes)`. /// Reserves memory for decoded data against the parser's budget. pub(crate) fn from_bytes(input: &'a [u8], parser: &mut Parser) -> MltRefResult<'a, Self> { let (input, size) = parse_varint::(input)?; // tag is a varint, but we know fewer than 127 tags for now, // so we can use a faster u8 and fail if it is bigger than 127. let (input, tag) = parse_u8(input)?; // 1 byte must be parsed for the tag, so if size is 0, it's invalid let size = size.checked_sub(1).ok_or(MltError::ZeroLayerSize)?; let (input, value) = take(input, size)?; let layer = match tag { // For now, we only support tag 0x01 layers, but more will be added soon 1 => Layer::Tag01(Layer01::from_bytes(value, parser)?), tag => Layer::Unknown(Unknown { tag, value }), }; Ok((input, layer)) } /// Decode all columns and return a fully-decoded [`ParsedLayer`]. /// /// Consumes `self`. For partial / incremental decoding, destructure with /// `Layer::Tag01(lazy)` and call the individual methods on [`Layer01`]. pub fn decode_all(self, dec: &mut Decoder) -> MltResult> { match self { Layer::Tag01(lazy) => Ok(Layer::Tag01(lazy.decode_all(dec)?)), Layer::Unknown(u) => Ok(Layer::Unknown(u)), } } } ================================================ FILE: rust/mlt-core/src/decoder/mod.rs ================================================ mod analyze; mod column; #[cfg(all(not(test), feature = "arbitrary"))] pub mod fuzzing; mod geometry; mod id; mod iterators; mod layer; mod model; mod property; mod root; pub(crate) mod stream; mod tile; // ── Public API ──────────────────────────────────────────────────────────────── // ── Crate-internal re-exports ───────────────────────────────────────────────── // Allow internal modules to keep using `crate::decoder::*` paths without // reaching into sub-module paths explicitly. pub(crate) use geometry::{Geometry, RawGeometry}; pub use geometry::{GeometryType, GeometryValues}; pub use id::ParsedId; // pub (not pub(crate)) so __private module can re-export it pub(crate) use id::{Id, RawId, RawIdValue}; pub use iterators::{ ColumnRef, FeatureRef, Layer01FeatureIter, LendingIterator, PropName, PropValueRef, }; pub(crate) use model::Column; pub use model::{ ColumnType, Layer, Layer01, ParsedLayer, ParsedLayer01, PropKind, PropValue, TileFeature, TileLayer, Unknown, }; // Re-export strings sub-module so encoder can use `crate::decoder::strings::*` pub(crate) use property::strings; pub(crate) use property::{ DictRange, ParsedProperty, ParsedScalar, ParsedSharedDict, ParsedSharedDictItem, ParsedStrings, Property, RawFsstData, RawPlainData, RawPresence, RawProperty, RawScalar, RawSharedDict, RawSharedDictEncoding, RawSharedDictItem, RawStrings, RawStringsEncoding, }; pub use root::{Decoder, Parser}; pub(crate) use stream::model::{ DictionaryType, IntEncoding, LengthType, LogicalEncoding, LogicalTechnique, LogicalValue, Morton, OffsetType, PhysicalEncoding, RawStream, RleMeta, StreamMeta, StreamType, }; ================================================ FILE: rust/mlt-core/src/decoder/model.rs ================================================ use std::fmt; use num_enum::TryFromPrimitive; use crate::decoder::{Geometry, Id, Property}; use crate::{DecodeState, Lazy, Parsed}; /// A layer that can be one of the known types, or an unknown. /// /// The decode-state type parameter `S` mirrors [`Layer01<'a, S>`]: /// - `Layer<'a>` / `Layer<'a, Lazy>` — freshly parsed; columns may still be raw bytes. /// - `Layer<'a, Parsed>` — returned by [`Layer::decode_all`]; all columns are decoded. Use `ParsedLayer` alias. #[non_exhaustive] pub enum Layer<'a, S: DecodeState = Lazy> { /// MVT-compatible layer (tag = 1) Tag01(Layer01<'a, S>), /// Unknown layer with tag, size, and value Unknown(Unknown<'a>), } pub type ParsedLayer<'a> = Layer<'a, Parsed>; impl<'a, S: DecodeState> fmt::Debug for Layer<'a, S> where Layer01<'a, S>: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Tag01(l) => f.debug_tuple("Tag01").field(l).finish(), Self::Unknown(u) => f.debug_tuple("Unknown").field(u).finish(), } } } /// Unknown layer data, stored as encoded bytes. /// /// Returned inside [`Layer::Unknown`] for any layer tag that is not recognized /// by this version of the library. Consumers can inspect the tag and raw bytes /// to forward or log the layer without losing data. #[derive(Debug, Clone, Default, PartialEq)] pub struct Unknown<'a> { pub(crate) tag: u8, pub(crate) value: &'a [u8], } impl<'a> Unknown<'a> { /// The raw layer tag identifying this unrecognised layer type. #[must_use] pub fn tag(&self) -> u32 { u32::from(self.tag) } /// The raw encoded bytes of this layer's body. #[must_use] pub fn data(&self) -> &'a [u8] { self.value } } /// Column definition #[derive(Debug, PartialEq)] pub struct Column<'a> { pub(crate) typ: ColumnType, pub(crate) name: Option<&'a str>, pub(crate) children: Vec, } /// Column data type, as stored in the tile #[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)] #[repr(u8)] pub enum ColumnType { Id = 0, OptId = 1, LongId = 2, OptLongId = 3, Geometry = 4, Bool = 10, OptBool = 11, I8 = 12, OptI8 = 13, U8 = 14, OptU8 = 15, I32 = 16, OptI32 = 17, U32 = 18, OptU32 = 19, I64 = 20, OptI64 = 21, U64 = 22, OptU64 = 23, F32 = 24, OptF32 = 25, F64 = 26, OptF64 = 27, Str = 28, OptStr = 29, SharedDict = 30, } /// Representation of an MLT feature table layer with tag `0x01` during decoding. /// /// The type parameter `S` controls how columns are stored: /// /// - `Layer01<'a>` / `Layer01<'a, Lazy>` (default) — columns are [`LazyParsed`](crate::LazyParsed) enums /// that may be raw or decoded. Use [`Layer01::decode_all`] to transition to `Layer01`. /// /// - `Layer01<'a, Parsed>` — all columns are fully decoded. The fields `id`, `geometry`, and /// `properties` hold the parsed types directly, allowing infallible readonly access. /// There is a `ParsedLayer01<'a>` type alias for this. pub struct Layer01<'a, S: DecodeState = Lazy> { pub name: &'a str, pub extent: u32, pub(crate) id: Option>, pub(crate) geometry: Geometry<'a, S>, pub(crate) properties: Vec>, #[cfg(fuzzing)] pub(crate) layer_order: Vec, } pub type ParsedLayer01<'a> = Layer01<'a, Parsed>; impl<'a, S> fmt::Debug for Layer01<'a, S> where S: DecodeState, Option>: fmt::Debug, Geometry<'a, S>: fmt::Debug, Vec>: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut s = f.debug_struct("Layer01"); s.field("name", &self.name) .field("extent", &self.extent) .field("id", &self.id) .field("geometry", &self.geometry) .field("properties", &self.properties); #[cfg(fuzzing)] s.field("layer_order", &self.layer_order); s.finish() } } impl<'a, S> Clone for Layer01<'a, S> where S: DecodeState, Option>: Clone, Geometry<'a, S>: Clone, Vec>: Clone, { fn clone(&self) -> Self { Self { name: self.name, extent: self.extent, id: self.id.clone(), geometry: self.geometry.clone(), properties: self.properties.clone(), #[cfg(fuzzing)] layer_order: self.layer_order.clone(), } } } /// Row-oriented working form for the optimizer. /// /// All features are stored as a flat [`Vec`] so that sorting is /// a single `sort_by_cached_key` call. The `property_names` vec is parallel /// to every `TileFeature::properties` slice in this layer. #[derive(Debug, Clone, PartialEq)] pub struct TileLayer { pub name: String, pub extent: u32, /// Column names, parallel to `TileFeature::properties`. pub property_names: Vec, pub features: Vec, } /// A single map feature in row form. #[derive(Debug, Clone, PartialEq)] pub struct TileFeature { pub id: Option, /// Geometry as a [`geo_types`] form pub geometry: geo_types::Geometry, /// One value per property column, in the same order as /// [`TileLayer::property_names`]. pub properties: Vec, } /// A single typed value for one property of one feature. /// /// Mirrors the scalar variants of `ParsedProperty` at the per-feature /// level. `SharedDict` items are flattened: each sub-field becomes its own /// `PropValue::Str` entry in `TileFeature::properties`, with the /// corresponding entry in `TileLayer::property_names` set to /// `"prefix:suffix"`. #[derive(Debug, Clone, PartialEq)] pub enum PropValue { Bool(Option), I8(Option), U8(Option), I32(Option), U32(Option), I64(Option), U64(Option), F32(Option), F64(Option), Str(Option), } #[derive(Debug, Clone, Copy, PartialEq, Eq, strum::IntoStaticStr)] #[strum(serialize_all = "lowercase")] pub enum PropKind { Bool, I8, U8, I32, U32, I64, U64, F32, F64, Str, } impl From<&PropValue> for PropKind { fn from(prop: &PropValue) -> Self { match prop { PropValue::Bool(_) => Self::Bool, PropValue::I8(_) => Self::I8, PropValue::U8(_) => Self::U8, PropValue::I32(_) => Self::I32, PropValue::U32(_) => Self::U32, PropValue::I64(_) => Self::I64, PropValue::U64(_) => Self::U64, PropValue::F32(_) => Self::F32, PropValue::F64(_) => Self::F64, PropValue::Str(_) => Self::Str, } } } ================================================ FILE: rust/mlt-core/src/decoder/property/decode.rs ================================================ use crate::decoder::{ParsedProperty, ParsedScalar, RawPresence, RawProperty}; use crate::utils::decode_presence; use crate::{Decode, Decoder, MltResult}; impl<'a, T: Copy + PartialEq> ParsedScalar<'a, T> { pub fn from_parts( name: &'a str, presence: RawPresence<'a>, values: Vec, dec: &mut Decoder, ) -> MltResult { let presence = decode_presence(presence, values, dec)?; Ok(Self { name, presence }) } } impl<'a> Decode> for RawProperty<'a> { /// Decode into a [`ParsedProperty`], charging `dec` for every heap allocation. /// /// For scalar columns the output size is known from stream metadata, so /// the budget is charged *before* decoding. For string and shared-dict /// columns the exact decoded size depends on compression, so the budget is /// charged *after* decoding based on actual allocation sizes. fn decode(self, dec: &mut Decoder) -> MltResult> { /// Decode the dense value stream and wrap it with the presence bitmap. /// `$decode_method` is the typed `RawStream` method for element type `$ty`. macro_rules! scalar_decode { ($variant:ident, $ty:ty, $decode_method:ident, $v:expr, $dec:expr) => {{ ParsedProperty::$variant(ParsedScalar::from_parts( $v.name, $v.presence, $v.data.$decode_method($dec)?, $dec, )?) }}; } Ok(match self { Self::Bool(v) => scalar_decode!(Bool, bool, decode_bools, v, dec), Self::I8(v) => scalar_decode!(I8, i8, decode_i8s, v, dec), Self::U8(v) => scalar_decode!(U8, u8, decode_u8s, v, dec), Self::I32(v) => scalar_decode!(I32, i32, decode_i32s, v, dec), Self::U32(v) => scalar_decode!(U32, u32, decode_u32s, v, dec), Self::I64(v) => scalar_decode!(I64, i64, decode_i64s, v, dec), Self::U64(v) => scalar_decode!(U64, u64, decode_u64s, v, dec), Self::F32(v) => scalar_decode!(F32, f32, decode_f32s, v, dec), Self::F64(v) => scalar_decode!(F64, f64, decode_f64s, v, dec), Self::Str(v) => ParsedProperty::Str(v.decode(dec)?), Self::SharedDict(v) => ParsedProperty::SharedDict(v.decode(dec)?), }) } } ================================================ FILE: rust/mlt-core/src/decoder/property/geojson.rs ================================================ use crate::ParsedProperty; impl ParsedProperty<'_> { #[must_use] pub fn name(&self) -> &str { match self { Self::Bool(v) => v.name, Self::I8(v) => v.name, Self::U8(v) => v.name, Self::I32(v) => v.name, Self::U32(v) => v.name, Self::I64(v) => v.name, Self::U64(v) => v.name, Self::F32(v) => v.name, Self::F64(v) => v.name, Self::Str(v) => v.name, Self::SharedDict(shared_dict) => shared_dict.prefix, } } } ================================================ FILE: rust/mlt-core/src/decoder/property/mod.rs ================================================ mod decode; mod model; pub(crate) mod strings; pub use model::*; ================================================ FILE: rust/mlt-core/src/decoder/property/model.rs ================================================ use std::borrow::Cow; use std::ops::Deref; use enum_dispatch::enum_dispatch; use crate::decoder::RawStream; use crate::utils::Presence; use crate::{DecodeState, Lazy}; /// Property column representation, parameterized by decode state. /// /// - `Property<'a>` / `Property<'a, Lazy>` — either raw bytes or decoded, in an [`crate::LazyParsed`] enum. /// - `Property<'a, Parsed>` — decoded [`ParsedProperty`] directly (no enum wrapper). pub type Property<'a, S = Lazy> = ::LazyOrParsed, ParsedProperty<'a>>; /// Raw scalar column (bool, integer, or float) as read directly from the tile. #[derive(Debug, Clone, PartialEq)] pub struct RawScalar<'a> { pub(crate) name: &'a str, pub(crate) presence: RawPresence<'a>, pub(crate) data: RawStream<'a>, } /// Raw string column as read directly from the tile. #[derive(Debug, Clone, PartialEq)] pub struct RawStrings<'a> { pub name: &'a str, pub presence: RawPresence<'a>, pub encoding: RawStringsEncoding<'a>, } /// Raw encoding payload for a string column (plain, dictionary, or FSST variants). /// /// `RawStream` order matches the encoder: see `StringEncoder.encode()`. #[derive(Debug, Clone, PartialEq)] pub enum RawStringsEncoding<'a> { /// Plain: length stream + data stream Plain(RawPlainData<'a>), /// Dictionary: lengths + offsets + dictionary data Dictionary { plain_data: RawPlainData<'a>, offsets: RawStream<'a>, }, /// FSST plain (4 streams): symbol lengths, symbol table, value lengths, compressed corpus. No offsets. FsstPlain(RawFsstData<'a>), /// FSST dictionary (5 streams): symbol lengths, symbol table, value lengths, compressed corpus, offsets. FsstDictionary { fsst_data: RawFsstData<'a>, offsets: RawStream<'a>, }, } /// Raw encoding payload for a `SharedDict` column. /// /// Unlike [`RawStringsEncoding`], shared dictionaries do NOT have their own offset stream. /// Instead, each child column has its own offset stream that references the shared dictionary. /// This is why only `Plain` and `FsstPlain` variants exist here. #[derive(Debug, Clone, PartialEq)] pub enum RawSharedDictEncoding<'a> { /// Plain shared dict (2 streams): lengths + data. Plain(RawPlainData<'a>), /// FSST plain shared dict (4 streams): symbol lengths, symbol table, lengths, corpus. FsstPlain(RawFsstData<'a>), } /// Raw shared-dictionary column as read directly from the tile. #[derive(Debug, Clone, PartialEq)] pub struct RawSharedDict<'a> { pub name: &'a str, pub encoding: RawSharedDictEncoding<'a>, pub children: Vec>, } /// Raw property data as read directly from the tile. #[derive(Debug, PartialEq, Clone)] pub enum RawProperty<'a> { Bool(RawScalar<'a>), I8(RawScalar<'a>), U8(RawScalar<'a>), I32(RawScalar<'a>), U32(RawScalar<'a>), I64(RawScalar<'a>), U64(RawScalar<'a>), F32(RawScalar<'a>), F64(RawScalar<'a>), Str(RawStrings<'a>), SharedDict(RawSharedDict<'a>), } /// Parsed property values in a typed enum form. #[derive(Clone, Debug, PartialEq, strum::IntoStaticStr)] #[strum(serialize_all = "snake_case")] #[enum_dispatch(Analyze)] pub enum ParsedProperty<'a> { Bool(ParsedScalar<'a, bool>), I8(ParsedScalar<'a, i8>), U8(ParsedScalar<'a, u8>), I32(ParsedScalar<'a, i32>), U32(ParsedScalar<'a, u32>), I64(ParsedScalar<'a, i64>), U64(ParsedScalar<'a, u64>), F32(ParsedScalar<'a, f32>), F64(ParsedScalar<'a, f64>), Str(ParsedStrings<'a>), SharedDict(ParsedSharedDict<'a>), } /// Decoded scalar property column (bool, integer, or float). /// /// `presence` carries both the optional bitvector and the dense values. /// For a non-optional column, `presence` is [`Presence::AllPresent`] with all /// values inline. For an optional column, `presence` is [`Presence::Bits`] with /// `bits.count_ones() == values.len()`. #[derive(Clone, Debug, PartialEq)] pub struct ParsedScalar<'a, T: Copy + PartialEq> { pub(crate) name: &'a str, pub(crate) presence: Presence<'a, T>, } impl<'a, T: Copy + PartialEq> Deref for ParsedScalar<'a, T> { type Target = Presence<'a, T>; #[inline] fn deref(&self) -> &Self::Target { &self.presence } } /// Per-feature byte range into a shared dictionary corpus. /// /// `start` and `end` are signed byte offsets into the corpus string. /// The sentinel value [`DictRange::NULL`] (`-1, -1`) indicates a NULL (absent) entry. /// Equal `start` and `end` indicate an empty string. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct DictRange { pub start: i32, pub end: i32, } impl DictRange { /// Sentinel value indicating a NULL (absent) entry. pub const NULL: Self = Self { start: -1, end: -1 }; } /// A single sub-property within a shared dictionary parsed value. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(all(not(test), feature = "arbitrary"), derive(arbitrary::Arbitrary))] pub struct ParsedSharedDictItem<'a> { /// The suffix name of this sub-property (appended to parent struct name). pub(crate) suffix: &'a str, /// Per-feature byte ranges into the parsed shared corpus. /// Non-null entries indicate a present string stored as /// `shared_dict.corpus()[start..end]`. /// [`DictRange::NULL`] indicates a NULL value. /// Equal `start` and `end` indicate an empty string. pub(crate) ranges: Vec, } /// Parsed string values for a single property. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ParsedStrings<'a> { pub(crate) name: &'a str, /// Per-feature cumulative end offsets into `data`. /// Non-negative values indicate a present string and store its exclusive /// end offset in `data`. /// Negative values indicate NULL and encode the current offset as `-end-1`, /// which is equivalent to `!end` in two's-complement form, /// so the next item can still recover its start offset without scanning back /// to the previous non-null value. This allows even the first item to be NULL. /// In other words, if `lengths == [5, 5, -6, 8]`, then the strings are: /// ```ignore /// data[0..5], // 0th string /// data[5..5], // 1st string is empty /// NULL, // 2nd string, offset stays 5 because -6 == -5-1 /// data[5..8], // 3rd string /// ``` pub(crate) lengths: Vec, pub(crate) data: Cow<'a, str>, } /// Parsed shared dictionary payload shared by one or more child string properties. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ParsedSharedDict<'a> { pub(crate) prefix: &'a str, pub(crate) data: Cow<'a, str>, pub(crate) items: Vec>, } /// A single child field within a `SharedDict` raw column #[derive(Clone, Debug, PartialEq)] pub struct RawSharedDictItem<'a> { pub name: &'a str, pub presence: RawPresence<'a>, pub data: RawStream<'a>, } /// Raw plain data (length stream + data stream) borrowed from input bytes. #[derive(Debug, Clone, PartialEq)] pub struct RawPlainData<'a> { pub lengths: RawStream<'a>, pub data: RawStream<'a>, } /// Raw FSST-compressed data (4 streams) borrowed from input bytes. #[derive(Debug, Clone, PartialEq)] pub struct RawFsstData<'a> { pub symbol_lengths: RawStream<'a>, pub symbol_table: RawStream<'a>, pub lengths: RawStream<'a>, pub corpus: RawStream<'a>, } /// Raw presence/nullability stream borrowed from input bytes. #[derive(Debug, Clone, PartialEq, Default)] pub struct RawPresence<'a>(pub Option>); ================================================ FILE: rust/mlt-core/src/decoder/property/strings.rs ================================================ use std::borrow::Cow; use crate::MltError::{BufferUnderflow, DictIndexOutOfBounds}; use crate::codecs::fsst::decode_fsst; use crate::decoder::{ DictionaryType, LengthType, OffsetType, ParsedSharedDict, ParsedSharedDictItem, ParsedStrings, RawFsstData, RawPlainData, RawPresence, RawSharedDictEncoding, RawStream, RawStrings, RawStringsEncoding, StreamType, }; use crate::errors::AsMltError as _; use crate::utils::AsUsize as _; use crate::{Decoder, DictRange, MltError, MltResult, RawSharedDict, RawSharedDictItem}; impl<'a> ParsedStrings<'a> { #[must_use] pub fn new(name: &'a str, lengths: Vec, data: Cow<'a, str>) -> Self { ParsedStrings { name, lengths, data, } } #[must_use] pub fn feature_count(&self) -> usize { self.lengths.len() } #[must_use] pub fn presence_bools(&self) -> Vec { self.lengths.iter().map(|&end| end >= 0).collect() } #[must_use] pub fn get(&self, idx: u32) -> Option<&str> { let idx = idx.as_usize(); let end = *self.lengths.get(idx)?; if end < 0 { return None; } let end = decode_end(end).as_usize(); let start = idx .checked_sub(1) .and_then(|prev| self.lengths.get(prev).copied()) .map_or(0, decode_end) .as_usize(); self.data.get(start..end) } #[must_use] pub fn dense_values(&self) -> Vec { let mut values = Vec::new(); let mut start = 0_u32; for &end in &self.lengths { let end_u32 = decode_end(end); let start_idx = start.as_usize(); let end_idx = end_u32.as_usize(); if end >= 0 && let Some(value) = self.data.get(start_idx..end_idx) { values.push(value.to_string()); } start = end_u32; } values } #[must_use] pub fn materialize(&self) -> Vec> { (0..u32::try_from(self.feature_count()).unwrap_or(u32::MAX)) .map(|i| self.get(i).map(str::to_string)) .collect() } } pub(crate) fn decode_shared_dict_range(range: DictRange) -> Option<(u32, u32)> { if let (Ok(start), Ok(end)) = (u32::try_from(range.start), u32::try_from(range.end)) { Some((start, end)) } else { None } } pub(crate) fn shared_dict_spans(lengths: &[u32], dec: &mut Decoder) -> MltResult> { let mut spans = dec.alloc(lengths.len())?; let mut offset = 0_u32; for &len in lengths { let start = offset; offset = offset.saturating_add(len); spans.push((start, offset)); } Ok(spans) } pub(crate) fn resolve_dict_spans( offsets: &[u32], presence: Option<&[bool]>, dict_spans: &[(u32, u32)], dec: &mut Decoder, ) -> MltResult>> { let present_count = presence.map_or(offsets.len(), <[bool]>::len); let mut resolved = dec.alloc(present_count)?; let mut next = offsets.iter().copied(); if let Some(presence) = presence { let fail = || { MltError::PresenceValueCountMismatch( presence.iter().filter(|&&v| v).count(), offsets.len(), ) }; for &present in presence { if !present { resolved.push(None); continue; } let idx = next.next().ok_or_else(fail)?; let span = dict_spans .get(idx as usize) .copied() .ok_or(DictIndexOutOfBounds(idx, dict_spans.len()))?; resolved.push(Some(span)); } if next.next().is_some() { return Err(fail()); } } else { for &idx in offsets { let span = dict_spans .get(idx as usize) .copied() .ok_or(DictIndexOutOfBounds(idx, dict_spans.len()))?; resolved.push(Some(span)); } } Ok(resolved) } fn dict_span_str(dict_data: &str, span: (u32, u32)) -> MltResult<&str> { let start = span.0.as_usize(); let end = span.1.as_usize(); let bytes = dict_data.as_bytes(); let Some(value) = bytes.get(start..end) else { let len = span.1.saturating_sub(span.0); return Err(BufferUnderflow(len, bytes.len().saturating_sub(start))); }; Ok(str::from_utf8(value)?) } impl ParsedSharedDict<'_> { #[must_use] pub fn corpus(&self) -> &str { &self.data } #[must_use] pub fn get(&self, span: (u32, u32)) -> Option<&str> { let start = span.0.as_usize(); let end = span.1.as_usize(); self.corpus().get(start..end) } } impl ParsedSharedDictItem<'_> { #[must_use] pub fn get<'a>(&self, shared_dict: &'a ParsedSharedDict<'_>, i: usize) -> Option<&'a str> { self.ranges .get(i) .copied() .and_then(decode_shared_dict_range) .and_then(|span| shared_dict.get(span)) } } impl<'a> RawPlainData<'a> { pub fn new(lengths: RawStream<'a>, data: RawStream<'a>) -> MltResult { validate_stream!( lengths, StreamType::Length(LengthType::VarBinary | LengthType::Dictionary) ); validate_stream!( data, StreamType::Data( DictionaryType::None | DictionaryType::Single | DictionaryType::Shared ) ); Ok(Self { lengths, data }) } pub fn decode(self, dec: &mut Decoder) -> MltResult<(&'a str, Vec)> { Ok(( str::from_utf8(self.data.data)?, self.lengths.decode_u32s(dec)?, )) } } impl<'a> RawFsstData<'a> { pub fn new( symbol_lengths: RawStream<'a>, symbol_table: RawStream<'a>, lengths: RawStream<'a>, corpus: RawStream<'a>, ) -> MltResult { validate_stream!(symbol_lengths, StreamType::Length(LengthType::Symbol)); validate_stream!(symbol_table, StreamType::Data(DictionaryType::Fsst)); validate_stream!(lengths, StreamType::Length(LengthType::Dictionary)); validate_stream!( corpus, StreamType::Data(DictionaryType::Single | DictionaryType::Shared) ); Ok(Self { symbol_lengths, symbol_table, lengths, corpus, }) } pub fn decode(self, dec: &mut Decoder) -> MltResult<(String, Vec)> { decode_fsst(self, dec) } } impl<'a> RawStringsEncoding<'a> { #[must_use] pub fn plain(plain_data: RawPlainData<'a>) -> Self { Self::Plain(plain_data) } pub fn dictionary(plain_data: RawPlainData<'a>, offsets: RawStream<'a>) -> MltResult { validate_stream!(offsets, StreamType::Offset(OffsetType::String)); Ok(Self::Dictionary { plain_data, offsets, }) } #[must_use] pub fn fsst_plain(fsst_data: RawFsstData<'a>) -> Self { Self::FsstPlain(fsst_data) } pub fn fsst_dictionary(fsst_data: RawFsstData<'a>, offsets: RawStream<'a>) -> MltResult { validate_stream!(offsets, StreamType::Offset(OffsetType::String)); Ok(Self::FsstDictionary { fsst_data, offsets }) } } impl<'a> RawSharedDictEncoding<'a> { /// Plain shared dict (2 streams): lengths + data. #[must_use] pub fn plain(plain_data: RawPlainData<'a>) -> Self { Self::Plain(plain_data) } /// FSST plain shared dict (4 streams): symbol lengths, symbol table, lengths, corpus. #[must_use] pub fn fsst_plain(fsst_data: RawFsstData<'a>) -> Self { Self::FsstPlain(fsst_data) } } impl<'a> RawStrings<'a> { #[must_use] pub fn new(name: &'a str, presence: RawPresence<'a>, encoding: RawStringsEncoding<'a>) -> Self { Self { name, presence, encoding, } } /// Decode string property from its encoded column. pub fn decode(self, dec: &mut Decoder) -> MltResult> { let name = self.name; let presence = match self.presence.0 { Some(s) => Some(s.decode_bools(dec)?), None => None, }; let parsed = match self.encoding { RawStringsEncoding::Plain(plain_data) => { let (data, lengths) = plain_data.decode(dec)?; ParsedStrings { name, lengths: to_absolute_lengths(&lengths, presence.as_deref(), dec)?, data: data.into(), } } RawStringsEncoding::Dictionary { plain_data, offsets, } => { let (data, lengths) = plain_data.decode(dec)?; let offsets: Vec = offsets.decode_u32s(dec)?; decode_dictionary_strings(name, &lengths, &offsets, presence.as_deref(), data, dec)? } RawStringsEncoding::FsstPlain(fsst_data) => { let (data, dict_lens) = fsst_data.decode(dec)?; ParsedStrings { name, lengths: to_absolute_lengths(&dict_lens, presence.as_deref(), dec)?, data: data.into(), } } RawStringsEncoding::FsstDictionary { fsst_data, offsets } => { let (data, lengths) = fsst_data.decode(dec)?; let offsets: Vec = offsets.decode_u32s(dec)?; decode_dictionary_strings( name, &lengths, &offsets, presence.as_deref(), &data, dec, )? } }; Ok(parsed) } } fn to_absolute_lengths( lengths: &[u32], presence: Option<&[bool]>, dec: &mut Decoder, ) -> MltResult> { let capacity = presence.map_or(lengths.len(), <[bool]>::len); let mut absolute = dec.alloc(capacity)?; let mut iter = lengths.iter().copied(); let mut end = 0_i32; if let Some(presence) = presence { for &present in presence { if present { let len = iter.next().ok_or(MltError::PresenceValueCountMismatch( presence.len(), lengths.len(), ))?; end = checked_absolute_end(end, len)?; absolute.push(end); } else { absolute.push(encode_null_end(end)); } } if iter.next().is_some() { return Err(MltError::PresenceValueCountMismatch( presence.iter().filter(|v| **v).count(), lengths.len(), )); } } else { for &len in lengths { end = checked_absolute_end(end, len)?; absolute.push(end); } } Ok(absolute) } fn decode_dictionary_strings<'a>( name: &'a str, dict_lengths: &[u32], offsets: &[u32], presence: Option<&[bool]>, dict_data: &str, dec: &mut Decoder, ) -> MltResult> { let dict_spans = shared_dict_spans(dict_lengths, dec)?; let resolved_spans = resolve_dict_spans(offsets, presence, &dict_spans, dec)?; let mut lengths = dec.alloc(resolved_spans.len())?; let mut data = String::new(); let mut end = 0_i32; for span in resolved_spans { if let Some(span) = span { let value = dict_span_str(dict_data, span)?; data.push_str(value); end = checked_string_end(end, value.len())?; lengths.push(end); } else { lengths.push(encode_null_end(end)); } } Ok(ParsedStrings { name, lengths, data: Cow::Owned(data), }) } pub(crate) fn encode_null_end(end: i32) -> i32 { -end - 1 } fn decode_end(end: i32) -> u32 { if end >= 0 { u32::try_from(end).expect("non-negative decoded string end must fit in u32") } else { u32::try_from(-i64::from(end) - 1).expect("encoded null boundary must fit in u32") } } pub(crate) fn checked_string_end(current_end: i32, byte_len: usize) -> MltResult { let byte_len = u32::try_from(byte_len)?; checked_absolute_end(current_end, byte_len) } pub(crate) fn checked_absolute_end(current_end: i32, delta: u32) -> MltResult { let delta = i32::try_from(delta)?; current_end.checked_add(delta).or_overflow() } impl<'a> RawSharedDict<'a> { #[must_use] pub fn new( name: &'a str, encoding: RawSharedDictEncoding<'a>, children: Vec>, ) -> Self { Self { name, encoding, children, } } /// Decode a shared-dictionary column into its decoded form. pub fn decode(self, dec: &mut Decoder) -> MltResult> { let prefix = self.name; let (data, dict_spans) = match self.encoding { RawSharedDictEncoding::Plain(plain_data) => { let (decoded, lengths) = plain_data.decode(dec)?; let dict_spans = shared_dict_spans(&lengths, dec)?; (Cow::Borrowed(decoded), dict_spans) } RawSharedDictEncoding::FsstPlain(fsst_data) => { let (decoded, lengths) = fsst_data.decode(dec)?; let dict_spans = shared_dict_spans(&lengths, dec)?; (decoded.into(), dict_spans) } }; let mut items = Vec::with_capacity(self.children.len()); for child in self.children { let offsets: Vec = child.data.decode_u32s(dec)?; let presence = match child.presence.0 { Some(s) => Some(s.decode_bools(dec)?), None => None, }; let ranges = resolve_dict_spans(&offsets, presence.as_deref(), &dict_spans, dec)? .into_iter() .map(|span| match span { Some(span) => encode_shared_dict_range(span.0, span.1), None => Ok(DictRange::NULL), }) .collect::, _>>()?; items.push(ParsedSharedDictItem { suffix: child.name, ranges, }); } let parsed = ParsedSharedDict { prefix, data, items, }; // Corpus size is only known after decompression; charge after. let bytes = parsed.items.iter().try_fold( u32::try_from(parsed.data.len()).or_overflow()?, |acc, item| { let n = u32::try_from(item.ranges.len() * size_of::()).or_overflow()?; acc.checked_add(n).or_overflow() }, )?; dec.consume(bytes)?; Ok(parsed) } } pub(crate) fn encode_shared_dict_range(start: u32, end: u32) -> MltResult { Ok(DictRange { start: i32::try_from(start)?, end: i32::try_from(end)?, }) } ================================================ FILE: rust/mlt-core/src/decoder/root.rs ================================================ use crate::LazyParsed::Raw; use crate::MltError::{ BufferUnderflow, GeometryWithoutStreams, InvalidSharedDictStreamCount, MissingGeometry, MultipleGeometryColumns, MultipleIdColumns, SharedDictRequiresStreams, TrailingLayerData, UnexpectedStructChildCount, UnsupportedStringStreamCount, }; use crate::codecs::varint::parse_varint; use crate::decoder::{ Column, ColumnType, DictionaryType, Geometry, Id, Layer01, ParsedLayer01, RawFsstData, RawGeometry, RawId, RawIdValue, RawPlainData, RawPresence, RawProperty, RawScalar, RawSharedDict, RawSharedDictEncoding, RawSharedDictItem, RawStream, RawStrings, RawStringsEncoding, StreamType, }; use crate::errors::AsMltError as _; use crate::utils::{AsUsize as _, SetOptionOnce as _, parse_string}; use crate::{Layer, Lazy, MltError, MltRefResult, MltResult, ParsedLayer}; /// Default memory budget: 20 MiB. const DEFAULT_MAX_BYTES: u32 = 20 * 1024 * 1024; /// Stateful decoder that enforces a per-tile memory budget during decoding. /// /// Pass a `Decoder` to every `raw.decode()` / `into_tile()` call and to /// `from_bytes`-style parsers. Each method charges the budget before /// performing heap allocations, so the total heap used never exceeds `max_bytes` /// (in bytes). /// /// ``` /// use mlt_core::Decoder; /// /// // Default: 10 MiB budget. /// let mut dec = Decoder::default(); /// /// // Custom budget. /// let mut dec = Decoder::with_max_size(64 * 1024 * 1024); /// ``` #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct Decoder { /// Keep track of the memory used when decoding a tile: raw->parsed transition budget: MemBudget, /// Reusable scratch buffer for the physical u32 decode pass. /// Held here so its heap allocation is reused across streams without extra cost. pub(crate) buffer_u32: Vec, /// Reusable scratch buffer for the physical u64 decode pass. /// Held here so its heap allocation is reused across streams without extra cost. pub(crate) buffer_u64: Vec, } impl Decoder { /// Create a decoder with a custom memory budget (in bytes). #[must_use] pub fn with_max_size(max_bytes: u32) -> Self { Self { budget: MemBudget::with_max_size(max_bytes), ..Default::default() } } pub fn decode_all<'a>( &mut self, layers: impl IntoIterator>, ) -> MltResult>> { layers .into_iter() .map(|l| l.decode_all(self)) .collect::>() } /// Allocate a `Vec` with the given capacity, charging the decoder's budget for /// `capacity * size_of::()` bytes. Use this instead of `Vec::with_capacity` in decode paths. #[inline] pub(crate) fn alloc(&mut self, capacity: usize) -> MltResult> { let bytes = capacity.checked_mul(size_of::()).or_overflow()?; let bytes_u32 = u32::try_from(bytes).or_overflow()?; self.budget.consume(bytes_u32)?; Ok(Vec::with_capacity(capacity)) } /// Charge the budget for `size` raw bytes. Prefer [`consume_items`][Self::consume_items] /// when charging for a known-type collection. #[inline] pub(crate) fn consume(&mut self, size: u32) -> MltResult<()> { self.budget.consume(size) } /// Charge the budget for `count` items of type `T` (`count * size_of::()` bytes). #[inline] pub(crate) fn consume_items(&mut self, count: usize) -> MltResult<()> { let bytes = count.checked_mul(size_of::()).or_overflow()?; self.budget.consume(u32::try_from(bytes).or_overflow()?) } #[inline] pub(crate) fn adjust(&mut self, adjustment: u32) { self.budget.adjust(adjustment); } /// Return the unused portion of a pre-charged allocation budget. /// /// Call this after fully populating a `Vec` that was pre-allocated with [`Decoder::alloc`], /// passing the same `alloc_size` that was given to `alloc`. /// /// Returns an error if the vector grew beyond `alloc_size` (malformed input caused more items /// than declared). Subtracts `(alloc_size - buf.len()) * size_of::()` from the budget. #[inline] pub(crate) fn adjust_alloc(&mut self, buf: &[T], alloc_size: usize) -> MltResult<()> { if buf.len() > alloc_size { return Err(MltError::InvalidDecodingStreamSize(buf.len(), alloc_size)); } // Return the unused portion of the pre-charged budget. let unused = (alloc_size - buf.len()) * size_of::(); // unused fits in u32: it's at most alloc_size * size_of::(), which was checked to fit // in u32 when alloc() was called. Using saturating_cast to avoid a fallible conversion. #[expect( clippy::cast_possible_truncation, reason = "unused <= alloc_size * size_of::() which was verified to fit in u32 by alloc()" )] self.budget.adjust(unused as u32); Ok(()) } #[must_use] pub fn consumed(&self) -> u32 { self.budget.consumed() } /// Reset the memory budget to zero, keeping scratch buffers allocated. /// /// Call this between tiles when reusing a single `Decoder` for multiple /// decodes — the per-tile budget is enforced fresh, but the internal /// `buffer_u32` / `buffer_u64` scratch space is retained so it doesn't /// need to be re-allocated. /// /// # Safety / correctness precondition /// /// Only call this after dropping any decoded allocations returned from the /// previous tile. Resetting the budget while earlier decoded outputs are /// still alive makes the budget enforceable only per-tile and can bypass /// the stronger guarantee that total live heap tracked by this decoder /// never exceeds the configured maximum. pub fn reset_budget(&mut self) { self.budget.reset(); } } impl MemBudget { /// Reset tracked usage for a new decode window. /// /// Callers must ensure that allocations accounted for by the previous /// window are no longer live before resetting. fn reset(&mut self) { self.bytes_used = 0; } } /// Stateful parser that enforces a memory budget during parsing (binary → raw structures). /// /// The parse chain reserves memory before allocations so total heap stays within the limit. /// /// ``` /// use mlt_core::Parser; /// /// # let bytes: &[u8] = &[]; /// let mut parser = Parser::default(); /// let layers = parser.parse_layers(bytes).expect("parse"); /// /// // Or with a custom limit: /// let mut parser = Parser::with_max_size(64 * 1024 * 1024); /// ``` #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct Parser { budget: MemBudget, } impl Parser { /// Create a parser with a custom memory budget (in bytes). #[must_use] pub fn with_max_size(max_bytes: u32) -> Self { Self { budget: MemBudget::with_max_size(max_bytes), } } /// Parse a sequence of binary layers, reserving decoded memory against this parser's budget. pub fn parse_layers<'a>(&mut self, mut input: &'a [u8]) -> MltResult>> { let mut result = Vec::new(); while !input.is_empty() { let layer; (input, layer) = Layer::from_bytes(input, self)?; result.push(layer); } Ok(result) } /// Reserve `size` bytes from the parse budget. Used internally by the parse chain. #[inline] pub(crate) fn reserve(&mut self, size: u32) -> MltResult<()> { self.budget.consume(size) } #[must_use] pub fn reserved(&self) -> u32 { self.budget.consumed() } } #[derive(Debug, Clone, PartialEq, Eq)] struct MemBudget { /// Hard ceiling: total decoded bytes may not exceed this value. pub max_bytes: u32, /// Running total of used bytes so far. pub bytes_used: u32, } impl Default for MemBudget { /// Create a decoder with the default 10 MiB memory budget. fn default() -> Self { Self::with_max_size(DEFAULT_MAX_BYTES) } } impl MemBudget { /// Create a decoder with a custom memory budget (in bytes). #[must_use] fn with_max_size(max_bytes: u32) -> Self { Self { max_bytes, bytes_used: 0, } } /// Adjust previous consumption by `- adjustment` bytes. Will panic if used incorrectly. #[inline] fn adjust(&mut self, adjustment: u32) { self.bytes_used = self.bytes_used.checked_sub(adjustment).unwrap(); } /// Take `size` bytes from the allocation budget. Call this before the actual allocation. #[inline] fn consume(&mut self, size: u32) -> MltResult<()> { let accumulator = &mut self.bytes_used; let max_bytes = self.max_bytes; if let Some(new_value) = accumulator .checked_add(size) .and_then(|v| if v > max_bytes { None } else { Some(v) }) { *accumulator = new_value; Ok(()) } else { Err(MltError::MemoryLimitExceeded { limit: max_bytes, used: *accumulator, requested: size, }) } } fn consumed(&self) -> u32 { self.bytes_used } } impl<'a> Layer01<'a, Lazy> { /// Parse `v01::Layer` metadata, reserving decoded memory against the parser's budget. pub(crate) fn from_bytes(input: &'a [u8], parser: &mut Parser) -> MltResult { let (input, layer_name) = parse_string(input)?; let (input, extent) = parse_varint::(input)?; let (input, column_count) = parse_varint::(input)?; // Each column requires at least 1 byte (column type) if input.len() < column_count.as_usize() { return Err(BufferUnderflow(column_count, input.len())); } // !!!!!!! // WARNING: make sure to never use `let (input, ...)` after this point: input var is reused let (mut input, (col_info, prop_count)) = parse_columns_meta(input, column_count, parser)?; #[cfg(fuzzing)] let layer_order = col_info .iter() .map(|column| column.typ) .map(crate::decoder::fuzzing::LayerOrdering::from) .collect(); let mut properties = Vec::with_capacity(prop_count.as_usize()); let mut id_column: Option = None; let mut geometry: Option = None; for column in col_info { use crate::decoder::RawProperty as RP; let opt; let value; let name = column.name.unwrap_or(""); match column.typ { ColumnType::Id | ColumnType::OptId => { (input, opt) = parse_optional(column.typ, input, parser)?; (input, value) = RawStream::from_bytes(input, parser)?; id_column.set_once(Raw(RawId { presence: RawPresence(opt), value: RawIdValue::Id32(value), }))?; } ColumnType::LongId | ColumnType::OptLongId => { (input, opt) = parse_optional(column.typ, input, parser)?; (input, value) = RawStream::from_bytes(input, parser)?; id_column.set_once(Raw(RawId { presence: RawPresence(opt), value: RawIdValue::Id64(value), }))?; } ColumnType::Geometry => { input = parse_geometry_column(input, &mut geometry, parser)?; } ColumnType::Bool | ColumnType::OptBool => { (input, opt) = parse_optional(column.typ, input, parser)?; (input, value) = RawStream::parse_bool(input, parser)?; properties.push(Raw(RP::Bool(scalar(name, opt, value)))); } ColumnType::I8 | ColumnType::OptI8 => { (input, opt) = parse_optional(column.typ, input, parser)?; (input, value) = RawStream::from_bytes(input, parser)?; properties.push(Raw(RP::I8(scalar(name, opt, value)))); } ColumnType::U8 | ColumnType::OptU8 => { (input, opt) = parse_optional(column.typ, input, parser)?; (input, value) = RawStream::from_bytes(input, parser)?; properties.push(Raw(RP::U8(scalar(name, opt, value)))); } ColumnType::I32 | ColumnType::OptI32 => { (input, opt) = parse_optional(column.typ, input, parser)?; (input, value) = RawStream::from_bytes(input, parser)?; properties.push(Raw(RP::I32(scalar(name, opt, value)))); } ColumnType::U32 | ColumnType::OptU32 => { (input, opt) = parse_optional(column.typ, input, parser)?; (input, value) = RawStream::from_bytes(input, parser)?; properties.push(Raw(RP::U32(scalar(name, opt, value)))); } ColumnType::I64 | ColumnType::OptI64 => { (input, opt) = parse_optional(column.typ, input, parser)?; (input, value) = RawStream::from_bytes(input, parser)?; properties.push(Raw(RP::I64(scalar(name, opt, value)))); } ColumnType::U64 | ColumnType::OptU64 => { (input, opt) = parse_optional(column.typ, input, parser)?; (input, value) = RawStream::from_bytes(input, parser)?; properties.push(Raw(RP::U64(scalar(name, opt, value)))); } ColumnType::F32 | ColumnType::OptF32 => { (input, opt) = parse_optional(column.typ, input, parser)?; (input, value) = RawStream::from_bytes(input, parser)?; properties.push(Raw(RP::F32(scalar(name, opt, value)))); } ColumnType::F64 | ColumnType::OptF64 => { (input, opt) = parse_optional(column.typ, input, parser)?; (input, value) = RawStream::from_bytes(input, parser)?; properties.push(Raw(RP::F64(scalar(name, opt, value)))); } ColumnType::Str | ColumnType::OptStr => { let prop; (input, prop) = parse_str_column(input, name, column.typ, parser)?; properties.push(Raw(prop)); } ColumnType::SharedDict => { let prop; (input, prop) = parse_shared_dict_column(input, &column, parser)?; properties.push(Raw(prop)); } } } if input.is_empty() { Ok(Layer01 { name: layer_name, extent, id: id_column, geometry: geometry.ok_or(MissingGeometry)?, properties, #[cfg(fuzzing)] layer_order, }) } else { Err(TrailingLayerData(input.len())) } } /// Decode all columns and transition to [`Layer01`]. /// /// Consumes `self` (a `Layer01`) and returns a `Layer01` where every /// column field holds its parsed value directly, enabling infallible readonly access. pub fn decode_all(self, dec: &mut Decoder) -> MltResult> { Ok(Layer01 { name: self.name, extent: self.extent, id: self.id.map(|id| id.into_parsed(dec)).transpose()?, geometry: self.geometry.into_parsed(dec)?, properties: self .properties .into_iter() .map(|p| p.into_parsed(dec)) .collect::>>()?, #[cfg(fuzzing)] layer_order: self.layer_order, }) } } fn parse_struct_children<'a>( mut input: &'a [u8], column: &Column<'a>, parser: &mut Parser, ) -> MltRefResult<'a, Vec>> { let mut children = Vec::with_capacity(column.children.len()); for child in &column.children { let (inp, sc) = parse_varint::(input)?; let (inp, child_optional) = parse_optional(child.typ, inp, parser)?; let optional_stream_count = u32::from(child_optional.is_some()); if let Some(data_count) = sc.checked_sub(optional_stream_count) && data_count != 1 { return Err(UnexpectedStructChildCount(data_count)); } let (inp, child_data) = RawStream::from_bytes(inp, parser)?; children.push(RawSharedDictItem { name: child.name.unwrap_or(""), presence: RawPresence(child_optional), data: child_data, }); input = inp; } Ok((input, children)) } fn parse_optional<'a>( typ: ColumnType, input: &'a [u8], parser: &mut Parser, ) -> MltRefResult<'a, Option>> { if typ.is_optional() { let (input, optional) = RawStream::parse_bool(input, parser)?; Ok((input, Some(optional))) } else { Ok((input, None)) } } fn parse_geometry_column<'a>( input: &'a [u8], geometry: &mut Option>, parser: &mut Parser, ) -> MltResult<&'a [u8]> { let (input, stream_count) = parse_varint::(input)?; if stream_count == 0 { return Err(GeometryWithoutStreams); } // Each stream requires at least 1 byte (physical stream type) let stream_count_capa = stream_count.as_usize(); if input.len() < stream_count_capa { return Err(BufferUnderflow(stream_count, input.len())); } // metadata let (input, meta) = RawStream::from_bytes(input, parser)?; // geometry items let (input, items) = RawStream::parse_multiple(input, stream_count_capa - 1, parser)?; geometry.set_once(Raw(RawGeometry { meta, items }))?; Ok(input) } fn parse_str_column<'a>( mut input: &'a [u8], name: &'a str, typ: ColumnType, parser: &mut Parser, ) -> MltRefResult<'a, RawProperty<'a>> { let mut stream_count = { let stream_count_u32; (input, stream_count_u32) = parse_varint::(input)?; stream_count_u32.as_usize() }; let presence; (input, presence) = parse_optional(typ, input, parser)?; if presence.is_some() { if stream_count == 0 { return Err(UnsupportedStringStreamCount(stream_count)); } stream_count -= 1; } let mut str_streams = [None, None, None, None, None]; if stream_count > str_streams.len() { return Err(UnsupportedStringStreamCount(stream_count)); } for slot in str_streams.iter_mut().take(stream_count) { let stream; (input, stream) = RawStream::from_bytes(input, parser)?; *slot = Some(stream); } let encoding = match str_streams { [Some(s1), Some(s2), None, None, None] => { RawStringsEncoding::plain(RawPlainData::new(s1, s2)?) } [Some(s1), Some(s2), Some(s3), None, None] => { RawStringsEncoding::dictionary(RawPlainData::new(s1, s3)?, s2)? } [Some(s1), Some(s2), Some(s3), Some(s4), None] => { RawStringsEncoding::fsst_plain(RawFsstData::new(s1, s2, s3, s4)?) } [Some(s1), Some(s2), Some(s3), Some(s4), Some(s5)] => { RawStringsEncoding::fsst_dictionary(RawFsstData::new(s1, s2, s3, s4)?, s5)? } _ => Err(UnsupportedStringStreamCount(stream_count))?, }; Ok(( input, RawProperty::Str(RawStrings { name, presence: RawPresence(presence), encoding, }), )) } fn parse_shared_dict_column<'a>( mut input: &'a [u8], column: &Column<'a>, parser: &mut Parser, ) -> MltRefResult<'a, RawProperty<'a>> { // Read header streams until we hit the dictionary DATA(Single|Shared) stream. let stream_count; (input, stream_count) = parse_varint::(input)?; let mut dict_streams = [None, None, None, None, None]; let mut streams_taken = 0_usize; while streams_taken < stream_count.as_usize() { let stream; (input, stream) = RawStream::from_bytes(input, parser)?; let is_last = matches!( stream.meta.stream_type, StreamType::Data(DictionaryType::Single | DictionaryType::Shared) ); dict_streams[streams_taken] = Some(stream); streams_taken += 1; if is_last { break; } else if streams_taken >= dict_streams.len() { return Err(UnsupportedStringStreamCount(streams_taken + 1)); } } let children; (input, children) = parse_struct_children(input, column, parser)?; // Validate stream_count: must equal dict_streams + children + optional_children. let children_n = u32::try_from(children.len()).or_overflow()?; let optional_n = children .iter() .filter(|c| c.presence.0.is_some()) .count() .try_into() .or_overflow()?; let dict_n = u32::try_from(streams_taken).or_overflow()?; let expected = crate::utils::checked_sum3(dict_n, children_n, optional_n)?; // Java's encoder had a bug (fixed) that overcounted by 1: dict + 2*N + 1. // Accept that value too so that files produced by older Java encoders still parse. let java_legacy = expected.checked_add(1).or_overflow()?; if stream_count != expected && stream_count != java_legacy { return Err(InvalidSharedDictStreamCount { actual: stream_count, expected, }); } let name = column.name.unwrap_or(""); let encoding = match dict_streams { [Some(s1), Some(s2), None, None, None] => { RawSharedDictEncoding::plain(RawPlainData::new(s1, s2)?) } [Some(s1), Some(s2), Some(s3), Some(s4), None] => { RawSharedDictEncoding::fsst_plain(RawFsstData::new(s1, s2, s3, s4)?) } _ => Err(SharedDictRequiresStreams(streams_taken))?, }; Ok(( input, RawProperty::SharedDict(RawSharedDict { name, encoding, children, }), )) } fn parse_columns_meta<'a>( mut input: &'a [u8], column_count: u32, parser: &mut Parser, ) -> MltRefResult<'a, (Vec>, u32)> { use crate::decoder::ColumnType::{Geometry, Id, LongId, OptId, OptLongId, SharedDict}; let mut col_info = Vec::with_capacity(column_count.as_usize()); let mut geometries = 0; let mut ids = 0; for _ in 0..column_count { let mut typ; (input, typ) = Column::from_bytes(input, parser)?; match typ.typ { Geometry => geometries += 1, Id | OptId | LongId | OptLongId => ids += 1, SharedDict => { // Yes, we need to parse children right here; otherwise this messes up the next column let child_column_count; (input, child_column_count) = parse_varint::(input)?; // Each column requires at least 1 byte (ColumnType without a name) let child_col_capacity = child_column_count.as_usize(); if input.len() < child_col_capacity { return Err(BufferUnderflow(child_column_count, input.len())); } let mut children = Vec::with_capacity(child_col_capacity); for _ in 0..child_column_count { let child; (input, child) = Column::from_bytes(input, parser)?; children.push(child); } typ.children = children; } _ => {} } col_info.push(typ); } if geometries > 1 { return Err(MultipleGeometryColumns); } if ids > 1 { return Err(MultipleIdColumns); } Ok((input, (col_info, column_count - geometries - ids))) } fn scalar<'a>(name: &'a str, opt: Option>, value: RawStream<'a>) -> RawScalar<'a> { RawScalar { name, presence: RawPresence(opt), data: value, } } ================================================ FILE: rust/mlt-core/src/decoder/stream/analyze.rs ================================================ use crate::decoder::{RawStream, StreamMeta}; use crate::{Analyze, StatType}; impl Analyze for RawStream<'_> { fn collect_statistic(&self, stat: StatType) -> usize { self.data.collect_statistic(stat) } fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { cb(self.meta); } } impl Analyze for StreamMeta { fn collect_statistic(&self, stat: StatType) -> usize { if stat == StatType::DecodedMetaSize { size_of::() } else { 0 } } } ================================================ FILE: rust/mlt-core/src/decoder/stream/decode.rs ================================================ use std::borrow::Cow; use std::mem; use bitvec::prelude::{BitSlice, BitVec, Lsb0}; use bitvec::view::BitView as _; use crate::codecs::bytes::{decode_bytes_to_bools, decode_bytes_to_u32s, decode_bytes_to_u64s}; use crate::codecs::fastpfor::decode_fastpfor; use crate::codecs::rle::decode_byte_rle; use crate::codecs::varint::parse_varint_vec; use crate::decoder::{LogicalEncoding, LogicalValue, PhysicalEncoding, RawStream}; use crate::errors::{AsMltError as _, fail_if_invalid_stream_size}; use crate::utils::AsUsize as _; use crate::{Decoder, MltError, MltResult}; impl<'a> RawStream<'a> { /// Decode a presence/nullability stream into a packed bitvector. /// /// Borrows directly from tile bytes (zero-copy) when both logical and physical /// encodings are `None`; otherwise decompresses byte-RLE into an owned `BitVec`. /// The result is always truncated to exactly `num_values` bits. pub(crate) fn decode_bitvec(self, dec: &mut Decoder) -> MltResult>> { let num_values = self.meta.num_values.as_usize(); if self.meta.encoding.physical == PhysicalEncoding::VarInt { return Err(MltError::NotImplemented("varint presence decoding")); } if self.meta.encoding.logical == LogicalEncoding::None && self.meta.encoding.physical == PhysicalEncoding::None { // Zero-copy: raw tile bytes are the packed bitvector. let num_bytes = num_values.div_ceil(8); fail_if_invalid_stream_size(self.data.len(), num_bytes)?; Ok(Cow::Borrowed(&self.data.view_bits::()[..num_values])) } else { let num_bytes = num_values.div_ceil(8); let bytes = decode_byte_rle(self.data, num_bytes, dec)?; let mut bvec = BitVec::::from_vec(bytes); bvec.truncate(num_values); Ok(Cow::Owned(bvec)) } } /// Decode a boolean data stream: byte-RLE → packed bitmap → `Vec`, charging `dec`. pub fn decode_bools(self, dec: &mut Decoder) -> MltResult> { if self.meta.encoding.physical == PhysicalEncoding::VarInt { return Err(MltError::NotImplemented("varint bool decoding")); } let num_values = self.meta.num_values.as_usize(); let num_bytes = num_values.div_ceil(8); let decoded = decode_byte_rle(self.data, num_bytes, dec)?; decode_bytes_to_bools(&decoded, num_values, dec) } pub fn decode_i8s(self, dec: &mut Decoder) -> MltResult> { self.decode_i32s(dec)? .into_iter() .map(i8::try_from) .collect::, _>>() .map_err(Into::into) } pub fn decode_u8s(self, dec: &mut Decoder) -> MltResult> { self.decode_u32s(dec)? .into_iter() .map(u8::try_from) .collect::, _>>() .map_err(Into::into) } pub fn decode_i32s(self, dec: &mut Decoder) -> MltResult> { let meta = self.meta; // i32 always needs a logical transform (zigzag at minimum) — use scratch buffer. let mut buf = mem::take(&mut dec.buffer_u32); self.decode_bits_u32(&mut buf, dec)?; let result = LogicalValue::new(meta).decode_i32(&buf, dec); dec.buffer_u32 = buf; dec.buffer_u32.clear(); result } pub fn decode_u32s(self, dec: &mut Decoder) -> MltResult> { let meta = self.meta; if meta.encoding.logical == LogicalEncoding::None { // No logical transform: physical words are the output — decode into a fresh Vec. let mut out = Vec::new(); self.decode_bits_u32(&mut out, dec)?; Ok(out) } else { // Logical transform needed — use the reusable scratch buffer. let mut buf = mem::take(&mut dec.buffer_u32); self.decode_bits_u32(&mut buf, dec)?; let result = LogicalValue::new(meta).decode_u32(&buf, dec); dec.buffer_u32 = buf; dec.buffer_u32.clear(); result } } pub fn decode_u64s(self, dec: &mut Decoder) -> MltResult> { let meta = self.meta; if meta.encoding.logical == LogicalEncoding::None { // No logical transform: physical words are the output — decode into a fresh Vec. let mut out = Vec::new(); self.decode_bits_u64(&mut out, dec)?; Ok(out) } else { // Logical transform needed — use the reusable scratch buffer. let mut buf = mem::take(&mut dec.buffer_u64); self.decode_bits_u64(&mut buf, dec)?; let result = LogicalValue::new(meta).decode_u64(&buf, dec); dec.buffer_u64 = buf; dec.buffer_u64.clear(); result } } pub fn decode_i64s(self, dec: &mut Decoder) -> MltResult> { let meta = self.meta; // i64 always needs a logical transform (zigzag at minimum) — use scratch buffer. let mut buf = mem::take(&mut dec.buffer_u64); self.decode_bits_u64(&mut buf, dec)?; let result = LogicalValue::new(meta).decode_i64(&buf, dec); dec.buffer_u64 = buf; dec.buffer_u64.clear(); result } /// Decode a stream of f32 values from raw little-endian bytes, charging `dec`. pub fn decode_f32s(self, dec: &mut Decoder) -> MltResult> { if self.meta.encoding.physical == PhysicalEncoding::VarInt { return Err(MltError::NotImplemented("varint f32 decoding")); } let num = self.meta.num_values.as_usize(); dec.consume_items::(num)?; fail_if_invalid_stream_size(self.data.len(), num.checked_mul(4).or_overflow()?)?; Ok(self .data .chunks_exact(4) .map(|chunk| f32::from_le_bytes(chunk.try_into().expect("infallible: chunks_exact(4)"))) .collect()) } /// Decode a stream of f64 values from raw little-endian bytes, charging `dec`. pub fn decode_f64s(self, dec: &mut Decoder) -> MltResult> { if self.meta.encoding.physical == PhysicalEncoding::VarInt { return Err(MltError::NotImplemented("varint f64 decoding")); } let num = self.meta.num_values.as_usize(); fail_if_invalid_stream_size(self.data.len(), num.checked_mul(8).or_overflow()?)?; dec.consume_items::(num)?; Ok(self .data .chunks_exact(8) .map(|chunk| f64::from_le_bytes(chunk.try_into().expect("infallible: chunks_exact(8)"))) .collect()) } /// Physically decode the stream into `buf` as `u32` values. /// /// `buf` is cleared and filled with the decoded words. The caller owns the /// buffer and is responsible for deciding whether it constitutes a final /// persistent allocation (and therefore should be charged to a [`Decoder`]). pub fn decode_bits_u32(self, buf: &mut Vec, dec: &mut Decoder) -> MltResult<()> { buf.clear(); match self.meta.encoding.physical { PhysicalEncoding::None => { let (_, values) = decode_bytes_to_u32s(self.data, self.meta.num_values, dec)?; *buf = values; } PhysicalEncoding::FastPFor256 => { *buf = decode_fastpfor(self.data, self.meta.num_values, dec)?; } PhysicalEncoding::VarInt => { let (_, values) = parse_varint_vec::(self.data, self.meta.num_values, dec)?; *buf = values; } } Ok(()) } /// Physically decode the stream into `buf` as `u64` values. /// /// `buf` is cleared and filled with the decoded words. The caller owns the /// buffer and is responsible for deciding whether it constitutes a final /// persistent allocation (and therefore should be charged to a [`Decoder`]). pub fn decode_bits_u64(self, buf: &mut Vec, dec: &mut Decoder) -> MltResult<()> { buf.clear(); match self.meta.encoding.physical { PhysicalEncoding::None => { let (_, values) = decode_bytes_to_u64s(self.data, self.meta.num_values, dec)?; *buf = values; } PhysicalEncoding::FastPFor256 => { return Err(MltError::UnsupportedPhysicalEncoding( "FastPFOR decoding u64", )); } PhysicalEncoding::VarInt => { let (_, values) = parse_varint_vec::(self.data, self.meta.num_values, dec)?; *buf = values; } } Ok(()) } } ================================================ FILE: rust/mlt-core/src/decoder/stream/logical.rs ================================================ use std::fmt::Debug; use std::iter::repeat_n; use num_traits::{PrimInt, ToPrimitive as _}; use crate::MltError::{ParsingLogicalTechnique, RleRunLenInvalid, UnsupportedLogicalEncoding}; use crate::codecs::zigzag::{decode_componentwise_delta_vec2s, decode_zigzag, decode_zigzag_delta}; use crate::decoder::{LogicalEncoding, LogicalTechnique, LogicalValue, RleMeta, StreamMeta}; use crate::errors::{AsMltError as _, fail_if_invalid_stream_size}; use crate::utils::AsUsize as _; use crate::{Decoder, MltResult}; impl RleMeta { /// Decode RLE (Run-Length Encoding) data. /// Charges the decoder for the expanded output allocation. pub fn decode(self, data: &[T], dec: &mut Decoder) -> MltResult> { let expected_len = self.runs.as_usize().checked_mul(2).or_overflow()?; fail_if_invalid_stream_size(data.len(), expected_len)?; let (run_lens, values) = data.split_at(self.runs.as_usize()); fail_if_invalid_stream_size(self.num_rle_values, Self::calc_size(run_lens)?)?; let alloc_size = self.num_rle_values.as_usize(); let mut result = dec.alloc(alloc_size)?; for (&run_len, &val) in run_lens.iter().zip(values.iter()) { let run = run_len .to_usize() .ok_or_else(|| RleRunLenInvalid(run_len.to_i128().unwrap_or_default()))?; result.extend(repeat_n(val, run)); } dec.adjust_alloc(&result, alloc_size)?; Ok(result) } fn calc_size(run_lens: &[T]) -> MltResult { run_lens .iter() .try_fold(T::zero(), |a, v| a.checked_add(v)) .and_then(|v| v.to_u32()) .ok_or_else(|| RleRunLenInvalid(run_lens.len().to_i128().unwrap_or_default())) } } impl LogicalTechnique { pub fn parse(value: u8) -> MltResult { Self::try_from(value).or(Err(ParsingLogicalTechnique(value))) } } impl LogicalValue { #[must_use] pub fn new(meta: StreamMeta) -> Self { Self { meta } } /// Logically decode `data` (physically decoded u32 words) into `Vec`. /// /// Never called for `LogicalEncoding::None` — that case is handled directly /// in the bridge (physical buffer decoded into a fresh output Vec). pub fn decode_i32(self, data: &[u32], dec: &mut Decoder) -> MltResult> { match self.meta.encoding.logical { LogicalEncoding::None => decode_zigzag(data, dec), LogicalEncoding::Rle(v) => decode_zigzag(&v.decode(data, dec)?, dec), LogicalEncoding::ComponentwiseDelta => decode_componentwise_delta_vec2s(data, dec), LogicalEncoding::Delta => decode_zigzag_delta::(data, dec), LogicalEncoding::DeltaRle(v) => { let expanded = v.decode(data, dec)?; decode_zigzag_delta::(&expanded, dec) } LogicalEncoding::Morton(v) => v.decode_codes(data, dec), LogicalEncoding::MortonDelta(v) => v.decode_delta(data, dec), LogicalEncoding::MortonRle(_) => Err(UnsupportedLogicalEncoding( self.meta.encoding.logical, "i32 (MortonRle)", )), LogicalEncoding::PseudoDecimal => Err(UnsupportedLogicalEncoding( self.meta.encoding.logical, "i32", )), } } /// Logically decode `data` (physically decoded u32 words) into `Vec`. /// /// Not called for `LogicalEncoding::None` — that case is handled entirely /// in the bridge (physical buffer decoded directly into the output Vec). pub fn decode_u32(self, data: &[u32], dec: &mut Decoder) -> MltResult> { let num = self.meta.num_values.as_usize(); match self.meta.encoding.logical { LogicalEncoding::None => { // Caller should have used the direct-output path; this is a fallback. dec.consume_items::(num)?; Ok(data.to_vec()) } LogicalEncoding::Rle(rle) => rle.decode(data, dec), LogicalEncoding::Delta => decode_zigzag_delta::(data, dec), LogicalEncoding::DeltaRle(rle) => { decode_zigzag_delta::(&rle.decode(data, dec)?, dec) } _ => Err(UnsupportedLogicalEncoding( self.meta.encoding.logical, "u32", )), } } /// Logically decode `data` (physically decoded u64 words) into `Vec`. /// /// Never called for `LogicalEncoding::None` — that case is handled directly /// in the bridge (physical buffer decoded into a fresh output Vec). pub fn decode_i64(self, data: &[u64], dec: &mut Decoder) -> MltResult> { match self.meta.encoding.logical { LogicalEncoding::None => decode_zigzag(data, dec), LogicalEncoding::Delta => decode_zigzag_delta::(data, dec), LogicalEncoding::DeltaRle(rle) => { let expanded = rle.decode(data, dec)?; decode_zigzag_delta::(&expanded, dec) } LogicalEncoding::Rle(rle) => { // rle.decode() charges for expanded u64 vec; decode_zigzag charges for i64 vec let expanded = rle.decode(data, dec)?; decode_zigzag(&expanded, dec) } _ => Err(UnsupportedLogicalEncoding( self.meta.encoding.logical, "i64", )), } } /// Logically decode `data` (physically decoded u64 words) into `Vec`. /// /// Not called for `LogicalEncoding::None` — that case is handled entirely /// in the bridge (physical buffer decoded directly into the output Vec). pub fn decode_u64(self, data: &[u64], dec: &mut Decoder) -> MltResult> { let num = self.meta.num_values.as_usize(); match self.meta.encoding.logical { LogicalEncoding::None => { // Caller should have used the direct-output path; this is a fallback. dec.consume_items::(num)?; Ok(data.to_vec()) } LogicalEncoding::Rle(rle) => rle.decode(data, dec), LogicalEncoding::Delta => decode_zigzag_delta::(data, dec), LogicalEncoding::DeltaRle(rle) => { let expanded = rle.decode(data, dec)?; decode_zigzag_delta::(&expanded, dec) } _ => Err(UnsupportedLogicalEncoding( self.meta.encoding.logical, "u64", )), } } } #[cfg(test)] mod tests { use super::*; use crate::MltError::InvalidDecodingStreamSize; use crate::test_helpers::dec; #[test] fn test_decode_rle_empty() { let rle = RleMeta { runs: 0, num_rle_values: 0, }; assert!(rle.decode::(&[], &mut dec()).unwrap().is_empty()); } #[test] fn test_decode_rle_invalid_stream_size() { // Valid RLE for runs=2 needs 4 elements (2 run lengths + 2 values). Only 3 provided. let rle = RleMeta { runs: 2, num_rle_values: 3, }; let data = [1u32, 2, 3]; let err = rle.decode::(&data, &mut dec()).unwrap_err(); assert!(matches!(err, InvalidDecodingStreamSize(3, 4))); } } ================================================ FILE: rust/mlt-core/src/decoder/stream/mod.rs ================================================ mod analyze; mod decode; pub(crate) mod logical; pub(crate) mod model; mod parse; mod physical; ================================================ FILE: rust/mlt-core/src/decoder/stream/model.rs ================================================ use derive_debug::Dbg; use num_enum::TryFromPrimitive; use crate::utils::formatter::{bytes_dbg, compact_dbg}; use crate::{MltError, MltResult}; /// Logical encoding technique used for a column, as stored in the tile #[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)] #[repr(u8)] pub enum LogicalTechnique { None = 0, Delta = 1, ComponentwiseDelta = 2, Rle = 3, Morton = 4, PseudoDecimal = 5, } /// Metadata for RLE decoding /// TODO v2 optimizations: /// * runs is identical to half the size of the associated array /// * `num_rle_values` is identical to the size of the sum of the first half of the array. /// Computing checked sum should not be too expensive. #[derive(Debug, Clone, Copy, PartialEq)] pub struct RleMeta { pub(crate) runs: u32, pub(crate) num_rle_values: u32, } /// Metadata for Morton decoding #[derive(Debug, Clone, Copy, PartialEq)] pub struct Morton { /// Number of bits used pub(crate) bits: u32, /// Coordinate shift pub(crate) shift: u32, } impl Morton { pub fn new(bits: u32, shift: u32) -> MltResult { if bits <= 16 { Ok(Self { bits, shift }) } else { Err(MltError::InvalidMortonBits(bits)) } } } /// How should the stream be interpreted at the logical level (second pass of decoding) #[derive(Debug, Clone, Copy, PartialEq)] pub enum LogicalEncoding { None, Delta, DeltaRle(RleMeta), ComponentwiseDelta, Rle(RleMeta), Morton(Morton), MortonDelta(Morton), MortonRle(Morton), PseudoDecimal, } /// Carries the stream metadata needed to perform the logical decode pass. /// /// Construct with [`LogicalValue::new`] after the physical decode pass fills a /// `&[u32]` or `&[u64]` buffer, then call the appropriate `decode_*` method, /// passing that slice as `data`. #[derive(Debug, PartialEq)] pub struct LogicalValue { pub(crate) meta: StreamMeta, } // Physical encoding types /// Dictionary type used for a column, as stored in the tile #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, TryFromPrimitive)] #[repr(u8)] pub enum DictionaryType { None = 0, Single = 1, Shared = 2, Vertex = 3, Morton = 4, Fsst = 5, } /// Offset type used for a column, as stored in the tile #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, TryFromPrimitive)] #[repr(u8)] pub enum OffsetType { Vertex = 0, Index = 1, String = 2, Key = 3, } /// Length type used for a column, as stored in the tile #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, TryFromPrimitive)] #[repr(u8)] pub enum LengthType { VarBinary = 0, Geometries = 1, Parts = 2, Rings = 3, Triangles = 4, Symbol = 5, Dictionary = 6, } /// How should the stream be interpreted at the physical level (first pass of decoding) #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum StreamType { Present, Data(DictionaryType), Offset(OffsetType), Length(LengthType), } /// Physical encoding used for a column, as stored in the tile #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, TryFromPrimitive)] #[repr(u8)] pub enum PhysicalEncoding { None = 0, /// Preferred, tends to produce the best compression ratio and decoding performance. /// But currently limited to 32-bit integer. FastPFor256 = 1, /// Can produce better results in combination with a heavyweight compression scheme like `Gzip`. /// Simple compression scheme where the encoding is easier to implement compared to `FastPfor`. VarInt = 2, } // RawStream types #[derive(Debug, Clone, Copy, PartialEq)] pub struct IntEncoding { pub logical: LogicalEncoding, pub physical: PhysicalEncoding, } /// Metadata about an encoded stream #[derive(Clone, Copy, Dbg, PartialEq)] pub struct StreamMeta { #[dbg(formatter = "compact_dbg")] pub stream_type: StreamType, #[dbg(formatter = "compact_dbg")] pub encoding: IntEncoding, pub(crate) num_values: u32, } /// Representation of an encoded stream #[derive(Clone, Dbg, PartialEq)] pub struct RawStream<'a> { pub meta: StreamMeta, #[dbg(formatter = "bytes_dbg")] pub(crate) data: &'a [u8], } ================================================ FILE: rust/mlt-core/src/decoder/stream/parse.rs ================================================ use std::io; use integer_encoding::VarIntWriter as _; use crate::codecs::varint::parse_varint; use crate::decoder::{ IntEncoding, LogicalEncoding, LogicalTechnique, Morton, PhysicalEncoding, RawStream, RleMeta, StreamMeta, StreamType, }; use crate::errors::{AsMltError as _, fail_if_invalid_stream_size}; use crate::utils::{AsUsize as _, BinarySerializer as _, parse_u8, take}; use crate::{MltError, MltRefResult, MltResult, Parser}; impl IntEncoding { #[must_use] pub(crate) const fn new(logical: LogicalEncoding, physical: PhysicalEncoding) -> Self { Self { logical, physical } } #[must_use] pub(crate) const fn none() -> Self { Self::new(LogicalEncoding::None, PhysicalEncoding::None) } } impl StreamMeta { #[inline] pub(crate) fn new(stream_type: StreamType, encoding: IntEncoding, num_values: u32) -> Self { Self { stream_type, encoding, num_values, } } #[inline] pub(crate) fn new2( stream_type: StreamType, logical: LogicalEncoding, physical: PhysicalEncoding, num_values: usize, ) -> MltResult { let enc = IntEncoding::new(logical, physical); Ok(Self::new(stream_type, enc, u32::try_from(num_values)?)) } #[inline] pub(crate) fn new_none(stream_type: StreamType, num_values: usize) -> MltResult { let enc = IntEncoding::none(); Ok(Self::new(stream_type, enc, u32::try_from(num_values)?)) } /// Parse stream from the input /// /// If `is_bool` is true, compute RLE parameters for boolean streams /// automatically instead of reading them from the input. /// /// Returns the stream metadata and the size of the stream in bytes. /// Reserves an upper-bound estimate of decoded bytes (`num_values * 8`) on the parser /// for all stream types. RLE uses `num_rle_values * 8` since that is the actual expanded count. pub(crate) fn from_bytes<'a>( input: &'a [u8], is_bool: bool, parser: &mut Parser, ) -> MltRefResult<'a, (Self, u32)> { use crate::decoder::LogicalTechnique as LT; let (input, stream_type) = StreamType::from_bytes(input)?; let (input, val) = parse_u8(input)?; let logical1 = LT::parse(val >> 5)?; let logical2 = LT::parse((val >> 2) & 0x7)?; let physical_encoding = PhysicalEncoding::parse(val & 0x3)?; let (input, num_values) = parse_varint::(input)?; let (input, byte_length) = parse_varint::(input)?; let mut input = input; let logical_encoding = match (logical1, logical2) { (LT::None | LT::Delta | LT::ComponentwiseDelta | LT::PseudoDecimal, LT::None) => { // Reserve decoded memory upper bound: worst case u64 = 8 bytes per value let decoded_bytes = num_values.saturating_mul(8); parser.reserve(decoded_bytes)?; match logical1 { LT::None => LogicalEncoding::None, LT::Delta => LogicalEncoding::Delta, LT::ComponentwiseDelta => LogicalEncoding::ComponentwiseDelta, _ => LogicalEncoding::PseudoDecimal, } } (LT::Delta, LT::Rle) | (LT::Rle, LT::None) => { let runs; let num_rle_values; if is_bool { runs = num_values.div_ceil(8); num_rle_values = byte_length; } else { (input, runs) = parse_varint::(input)?; (input, num_rle_values) = parse_varint::(input)?; } // Reserve decoded memory (worst case: u64 = 8 bytes per value) let decoded_bytes = num_rle_values.saturating_mul(8); parser.reserve(decoded_bytes)?; let rle = RleMeta { runs, num_rle_values, }; if logical1 == LT::Rle { LogicalEncoding::Rle(rle) } else { LogicalEncoding::DeltaRle(rle) } } (LT::Morton, LT::None | LT::Rle | LT::Delta) => { // Reserve decoded memory upper bound: worst case u64 = 8 bytes per value let decoded_bytes = num_values.saturating_mul(8); parser.reserve(decoded_bytes)?; let bits; let shift; (input, bits) = parse_varint::(input)?; (input, shift) = parse_varint::(input)?; let morton = Morton::new(bits, shift)?; match logical2 { LT::Rle => LogicalEncoding::MortonRle(morton), LT::Delta => LogicalEncoding::MortonDelta(morton), _ => LogicalEncoding::Morton(morton), } } _ => Err(MltError::InvalidLogicalEncodings(logical1, logical2))?, }; let meta = Self::new( stream_type, IntEncoding::new(logical_encoding, physical_encoding), num_values, ); Ok((input, (meta, byte_length))) } pub(crate) fn write_to( &self, writer: &mut W, is_bool: bool, byte_length: u32, ) -> io::Result<()> { use LogicalEncoding as LE; use LogicalTechnique as LT; writer.write_u8(self.stream_type.as_u8())?; let logical_enc_u8: u8 = match self.encoding.logical { LE::None => (LT::None as u8) << 5, LE::Delta => (LT::Delta as u8) << 5, LE::DeltaRle(_) => ((LT::Delta as u8) << 5) | ((LT::Rle as u8) << 2), LE::ComponentwiseDelta => (LT::ComponentwiseDelta as u8) << 5, LE::Rle(_) => (LT::Rle as u8) << 5, LE::Morton(_) => (LT::Morton as u8) << 5, LE::MortonRle(_) => (LT::Morton as u8) << 5 | ((LT::Rle as u8) << 2), LE::MortonDelta(_) => (LT::Morton as u8) << 5 | ((LT::Delta as u8) << 2), LE::PseudoDecimal => (LT::PseudoDecimal as u8) << 5, }; let physical_enc_u8: u8 = match self.encoding.physical { PhysicalEncoding::None => 0x0, PhysicalEncoding::FastPFor256 => 0x1, PhysicalEncoding::VarInt => 0x2, }; writer.write_u8(logical_enc_u8 | physical_enc_u8)?; writer.write_varint(self.num_values)?; writer.write_varint(byte_length)?; // some encoding have settings inside them match self.encoding.logical { LE::DeltaRle(r) | LE::Rle(r) => { if !is_bool { writer.write_varint(r.runs)?; writer.write_varint(r.num_rle_values)?; } } LE::Morton(m) | LE::MortonDelta(m) | LE::MortonRle(m) => { writer.write_varint(m.bits)?; writer.write_varint(m.shift)?; } LE::None | LE::Delta | LE::ComponentwiseDelta | LE::PseudoDecimal => {} } Ok(()) } } impl<'a> RawStream<'a> { #[must_use] pub(crate) fn new(meta: StreamMeta, data: &'a [u8]) -> Self { Self { meta, data } } pub(crate) fn from_bytes(input: &'a [u8], parser: &mut Parser) -> MltRefResult<'a, Self> { Self::from_bytes_internal(input, false, parser) } pub(crate) fn parse_multiple( mut input: &'a [u8], count: usize, parser: &mut Parser, ) -> MltRefResult<'a, Vec> { let mut result = Vec::with_capacity(count); for _ in 0..count { let stream; (input, stream) = RawStream::from_bytes_internal(input, false, parser)?; result.push(stream); } Ok((input, result)) } pub(crate) fn parse_bool(input: &'a [u8], parser: &mut Parser) -> MltRefResult<'a, Self> { Self::from_bytes_internal(input, true, parser) } /// Parse stream from the input /// If `is_bool` is true, compute RLE parameters for boolean streams /// automatically instead of reading them from the input. /// For RLE streams with `VarInt` data, validates that run lengths sum to `num_rle_values`. fn from_bytes_internal( input: &'a [u8], is_bool: bool, parser: &mut Parser, ) -> MltRefResult<'a, Self> { use LogicalEncoding as LE; use PhysicalEncoding as PD; let (input, (meta, byte_length)) = StreamMeta::from_bytes(input, is_bool, parser)?; let (input, data) = take(input, byte_length)?; // For RLE with VarInt physical encoding, validate stream: run lengths must sum to num_rle_values if let LE::Rle(r) | LE::DeltaRle(r) = meta.encoding.logical && matches!(meta.encoding.physical, PD::VarInt) && !is_bool { validate_rle_varint_stream(data, r.runs, r.num_rle_values)?; } Ok((input, RawStream::new(meta, data))) } } /// Validate RLE stream data: first `runs` varints must sum to `num_rle_values`. fn validate_rle_varint_stream(data: &[u8], runs: u32, num_rle_values: u32) -> MltResult<()> { let mut rest = data; let mut sum: u64 = 0; for _ in 0..runs { let (next, len) = parse_varint::(rest)?; rest = next; sum = sum.checked_add(len.into()).or_overflow()?; } if sum != u64::from(num_rle_values) { let sum_usize = usize::try_from(sum).map_err(|_| MltError::IntegerOverflow)?; fail_if_invalid_stream_size(sum_usize, num_rle_values.as_usize())?; } Ok(()) } ================================================ FILE: rust/mlt-core/src/decoder/stream/physical.rs ================================================ use crate::MltError::ParsingStreamType; use crate::MltRefResult; use crate::decoder::{DictionaryType, LengthType, OffsetType, StreamType}; use crate::utils::parse_u8; impl StreamType { pub fn from_bytes(input: &'_ [u8]) -> MltRefResult<'_, Self> { let (input, value) = parse_u8(input)?; let pt = Self::from_u8(value).ok_or(ParsingStreamType(value))?; Ok((input, pt)) } fn from_u8(value: u8) -> Option { let high4 = value >> 4; let low4 = value & 0x0F; Some(match high4 { #[cfg(fuzzing)] // when fuzzing, we cannot have ignored bits, to preserve roundtrip-ability 0 if low4 == 0 => StreamType::Present, #[cfg(not(fuzzing))] 0 => Self::Present, 1 => Self::Data(DictionaryType::try_from(low4).ok()?), 2 => Self::Offset(OffsetType::try_from(low4).ok()?), 3 => Self::Length(LengthType::try_from(low4).ok()?), _ => return None, }) } #[must_use] pub fn as_u8(self) -> u8 { let proto_high4 = match self { Self::Present => 0, Self::Data(_) => 1, Self::Offset(_) => 2, Self::Length(_) => 3, }; let high4 = proto_high4 << 4; let low4 = match self { Self::Present => 0, Self::Data(i) => i as u8, Self::Offset(i) => i as u8, Self::Length(i) => i as u8, }; debug_assert!(low4 <= 0x0F, "secondary types should not exceed 4 bit"); high4 | low4 } } ================================================ FILE: rust/mlt-core/src/decoder/tile.rs ================================================ //! Row-oriented "source form" for the optimizer. //! //! [`TileLayer`] holds one [`TileFeature`] per map feature, each owning //! its geometry as a [`geo_types::Geometry`] and its property values as a //! plain `Vec`. This is the working form used throughout the //! optimizer and sorting pipeline: it is cheap to clone, trivially sortable, //! and free from any encoded/decoded duality. use crate::decoder::{ GeometryValues, Layer01, ParsedLayer01, ParsedProperty, PropValue, PropValueRef, TileFeature, TileLayer, }; use crate::errors::AsMltError as _; use crate::{Decoder, LendingIterator, MltResult}; impl ParsedLayer01<'_> { /// Returns the decoded geometry buffer for this layer. /// /// Provides access to the columnar geometry arrays (vertex buffer, offset arrays, geometry /// types) for advanced use cases such as building typed arrays for WebAssembly or /// performing spatial indexing. For iterating feature geometries as `geo_types` values, /// prefer [`iter_features`](Self::iter_features) instead. #[must_use] pub fn geometry_values(&self) -> &GeometryValues { &self.geometry } /// Decode and convert into a row-oriented [`TileLayer`], charging every /// heap allocation against `dec`. pub fn into_tile(self, dec: &mut Decoder) -> MltResult { // Extract owned/copied fields before borrowing self for the feature iterator. let name = self.name.to_string(); let extent = self.extent; let names: Vec = self.iterate_prop_names().map(|n| n.to_string()).collect(); let col_nulls = typed_nulls(&self.properties); let mut features = dec.alloc::(self.feature_count())?; let mut feat_iter = self.iter_features(); while let Some(feat) = feat_iter.next() { let feat = feat?; let mut values = dec.alloc::(names.len())?; for (col_idx, value) in feat.iter_all_properties().enumerate() { values.push(match value { Some(v) => prop_value_from_ref(v), None => col_nulls[col_idx].clone(), }); } charge_str_props(dec, &values)?; features.push(TileFeature { id: feat.id, geometry: feat.geometry, properties: values, }); } Ok(TileLayer { name, extent, property_names: names, features, }) } #[must_use] pub fn feature_count(&self) -> usize { self.geometry.vector_types.len() } } impl Layer01<'_> { /// Decode and convert into a row-oriented [`TileLayer`] pub fn into_tile(self, dec: &mut Decoder) -> MltResult { self.decode_all(dec)?.into_tile(dec) } } /// Convert a [`PropValueRef`] (as yielded by [`crate::FeatureRef::iter_all_properties`]) /// into an owned [`PropValue`]. fn prop_value_from_ref(value: PropValueRef<'_>) -> PropValue { match value { PropValueRef::Bool(v) => PropValue::Bool(Some(v)), PropValueRef::I8(v) => PropValue::I8(Some(v)), PropValueRef::U8(v) => PropValue::U8(Some(v)), PropValueRef::I32(v) => PropValue::I32(Some(v)), PropValueRef::U32(v) => PropValue::U32(Some(v)), PropValueRef::I64(v) => PropValue::I64(Some(v)), PropValueRef::U64(v) => PropValue::U64(Some(v)), PropValueRef::F32(v) => PropValue::F32(Some(v)), PropValueRef::F64(v) => PropValue::F64(Some(v)), PropValueRef::Str(s) => PropValue::Str(Some(s.to_string())), } } /// Build a flat list of typed null [`PropValue`]s, one per logical column position /// as yielded by [`crate::FeatureRef::iter_all_properties`]. /// /// Each scalar column contributes one entry with its specific null variant (e.g. /// `PropValue::Bool(None)`). A `SharedDict` column expands to one `PropValue::Str(None)` /// entry per sub-item. fn typed_nulls(properties: &[ParsedProperty<'_>]) -> Vec { use ParsedProperty as PP; use PropValue as PV; let mut nulls = Vec::new(); for prop in properties { match prop { PP::Bool(_) => nulls.push(PV::Bool(None)), PP::I8(_) => nulls.push(PV::I8(None)), PP::U8(_) => nulls.push(PV::U8(None)), PP::I32(_) => nulls.push(PV::I32(None)), PP::U32(_) => nulls.push(PV::U32(None)), PP::I64(_) => nulls.push(PV::I64(None)), PP::U64(_) => nulls.push(PV::U64(None)), PP::F32(_) => nulls.push(PV::F32(None)), PP::F64(_) => nulls.push(PV::F64(None)), PP::Str(_) => nulls.push(PV::Str(None)), PP::SharedDict(d) => { for _ in &d.items { nulls.push(PV::Str(None)); } } } } nulls } /// Charge `dec` for the heap bytes of owned `String` values inside `PropValue::Str`. fn charge_str_props(dec: &mut Decoder, props: &[PropValue]) -> MltResult<()> { let str_bytes = props .iter() .filter_map(|p| { if let PropValue::Str(Some(s)) = p { Some(s.len()) } else { None } }) .try_fold(0u32, |acc, n| { acc.checked_add(u32::try_from(n).or_overflow()?) .or_overflow() })?; if str_bytes > 0 { dec.consume(str_bytes)?; } Ok(()) } ================================================ FILE: rust/mlt-core/src/encoder/analyze.rs ================================================ #[cfg(any(test, feature = "__private"))] use crate::decoder::StreamMeta; use crate::decoder::{ParsedScalar, ParsedSharedDict, ParsedStrings}; #[cfg(any(test, feature = "__private"))] use crate::encoder::EncodedStream; use crate::{Analyze, StatType}; #[cfg(any(test, feature = "__private"))] impl Analyze for EncodedStream { fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { cb(self.meta); } } impl Analyze for ParsedScalar<'_, T> { fn collect_statistic(&self, stat: StatType) -> usize { let meta = if stat == StatType::DecodedMetaSize { self.name.len() } else { 0 }; meta + self.presence.collect_statistic(stat) } } impl Analyze for ParsedSharedDict<'_> { fn collect_statistic(&self, stat: StatType) -> usize { let meta = if stat == StatType::DecodedMetaSize { self.prefix.len() + self.items.iter().map(|v| v.suffix.len()).sum::() } else { 0 }; meta + self.data.len() } } impl Analyze for ParsedStrings<'_> { fn collect_statistic(&self, stat: StatType) -> usize { let meta = if stat == StatType::DecodedMetaSize { self.name.len() } else { 0 }; meta + self.dense_values().collect_statistic(stat) } } ================================================ FILE: rust/mlt-core/src/encoder/fuzzing.rs ================================================ use arbitrary::Error::IncorrectFormat; use arbitrary::{Arbitrary, Result, Unstructured}; use crate::encoder::model::StagedLayer; use crate::encoder::optimizer::Presence; use crate::encoder::{StagedId, StagedProperty, StagedSharedDict, StagedStrings}; impl Arbitrary<'_> for StagedId { fn arbitrary(u: &mut Unstructured<'_>) -> Result { // Bound ID count to prevent OOM from unbounded vector generation let count = u.int_in_range(0..=64u8)? as usize; let values: Vec> = (0..count).map(|_| u.arbitrary()).collect::>()?; Ok(Self::from_optional(values)) } } impl Arbitrary<'_> for StagedLayer { fn arbitrary(u: &mut Unstructured<'_>) -> Result { // Bound name length to prevent OOM from unbounded string generation let name_len = u.int_in_range(0..=32u8)? as usize; let name: String = (0..name_len) .map(|_| u.arbitrary::()) .collect::>()?; let extent: u32 = u.arbitrary()?; // Generate geometry first -- its feature count drives ID and property columns. let geometry: crate::decoder::GeometryValues = u.arbitrary()?; let fc = geometry.vector_types().len(); let id = if u.arbitrary::()? { let ids: Vec> = (0..fc) .map(|_| -> Result<_> { if u.arbitrary::()? { Ok(Some(u.arbitrary::()?)) } else { Ok(None) } }) .collect::>()?; StagedId::from_optional(ids) } else { StagedId::None }; // Bound property count to prevent OOM from unbounded vector generation. // Each column must have exactly `fc` values to match the feature count. let prop_count = u.int_in_range(0..=4u8)? as usize; let properties: Vec = (0..prop_count) .map(|_| { let values: Vec> = (0..fc).map(|_| u.arbitrary()).collect::>()?; Ok(StagedProperty::opt_u32("prop", values)) }) .collect::>()?; Ok(Self { name, extent, id, geometry, properties, }) } } impl<'a> Arbitrary<'a> for StagedSharedDict { fn arbitrary(u: &mut Unstructured<'a>) -> Result { // Bound item count and string sizes to prevent OOM let item_count = u.int_in_range(0..=8u8)? as usize; let items_raw: Vec<(String, Vec>, Presence)> = (0..item_count) .map(|_| { let values = generate_strings(u)?; let presence = if values.iter().all(Option::is_some) { Presence::AllPresent } else { Presence::Mixed }; Ok((bounded_string(u, 32)?, values, presence)) }) .collect::>()?; if items_raw.is_empty() { return Ok(Self { prefix: bounded_string(u, 32)?, data: String::new(), items: Vec::new(), }); } let prefix = bounded_string(u, 32)?; Self::new(prefix, items_raw).map_err(|_| IncorrectFormat) } } impl Arbitrary<'_> for StagedProperty { fn arbitrary(u: &mut Unstructured<'_>) -> Result { // Bound value count to prevent OOM from unbounded vector generation let count = u.int_in_range(0..=64u8)? as usize; let values: Vec> = (0..count).map(|_| u.arbitrary()).collect::>()?; Ok(Self::opt_u32("prop", values)) } } impl Arbitrary<'_> for StagedStrings { fn arbitrary(u: &mut Unstructured<'_>) -> Result { Ok(Self::from_optional( bounded_string(u, 32)?, generate_strings(u)?, )) } } /// Generate a string with bounded length to prevent OOM from unbounded string generation. pub fn bounded_string(u: &mut Unstructured<'_>, max_len: u8) -> Result { let len = u.int_in_range(0..=max_len)? as usize; (0..len) .map(|_| u.arbitrary::()) .collect::>() } fn generate_strings(u: &mut Unstructured) -> Result>> { // Bound string count and individual string lengths to prevent OOM let val_count = u.int_in_range(0..=16u8)? as usize; let values: Vec> = (0..val_count) .map(|_| -> Result<_> { if u.arbitrary()? { Ok(Some(bounded_string(u, 64)?)) } else { Ok(None) } }) .collect::>()?; Ok(values) } ================================================ FILE: rust/mlt-core/src/encoder/geometry/encode.rs ================================================ use std::collections::HashMap; use std::mem; use geo_types::Coord; use probabilistic_collections::SipHasherBuilder; use probabilistic_collections::hyperloglog::HyperLogLog; use super::model::VertexBufferType; use crate::MltResult; use crate::codecs::hilbert::hilbert_sort_key; use crate::codecs::zigzag::encode_componentwise_delta_vec2s; use crate::decoder::GeometryType::{LineString, Point, Polygon}; use crate::decoder::{ ColumnType, DictionaryType, GeometryType, GeometryValues, LengthType, LogicalEncoding, Morton, OffsetType, PhysicalEncoding, StreamMeta, StreamType, }; use crate::encoder::model::{CurveParams, StreamCtx}; use crate::encoder::{Codecs, Encoder, PhysicalCodecs, write_stream_payload}; use crate::utils::AsUsize as _; /// Compute `ZOrderCurve` parameters from the vertex value range. /// /// Returns `(bits, shift)` matching Java's `SpaceFillingCurve`. /// Build a sorted unique Morton dictionary and per-vertex offset indices from a flat /// `[x0, y0, x1, y1, …]` vertex slice. /// /// Returns `(sorted_unique_codes, per_vertex_offsets)`. #[hotpath::measure] fn build_morton_dict(vertices: &[i32], meta: Morton) -> MltResult<(Vec, Vec)> { let codes: Vec = vertices .chunks_exact(2) .map(|c| meta.encode_morton(c[0], c[1])) .collect::>()?; let mut dict = codes.clone(); dict.sort_unstable(); dict.dedup(); #[expect( clippy::cast_possible_truncation, reason = "dict.len() <= u32::MAX (deduped u32 codes)" )] let code_to_idx: HashMap = dict .iter() .enumerate() .map(|(i, &c)| (c, i as u32)) .collect(); let offsets: Vec = codes.iter().map(|code| code_to_idx[code]).collect(); Ok((dict, offsets)) } /// Build a Hilbert-curve-sorted unique vertex dictionary into caller-provided /// scratch. /// /// On return, `dict_xy` holds the deduplicated `[x, y, …]` dictionary in /// Hilbert order and `offsets[i]` is the slot of input vertex `i`; `indexed` /// and `remap` are left as opaque scratch. /// /// Dedup is keyed on the Hilbert curve index. Inside the `params.bits` grid /// the index ↔ `(x, y)` mapping is bijective, so dedup-by-index is equivalent /// to dedup-by-coordinate without the cost of hashing pairs. #[hotpath::measure] fn build_hilbert_dict( vertices: &[i32], params: CurveParams, offsets: &mut Vec, indexed: &mut Vec, dict_xy: &mut Vec, remap: &mut HashMap, ) { offsets.clear(); indexed.clear(); dict_xy.clear(); remap.clear(); let coord_count = vertices.len() / 2; if coord_count == 0 { return; } offsets.reserve(coord_count); indexed.reserve(coord_count); dict_xy.reserve(coord_count * 2); remap.reserve(coord_count); for (i, c) in vertices.chunks_exact(2).enumerate() { let k = hilbert_sort_key(Coord { x: c[0], y: c[1] }, params); offsets.push(k); // Key in the high 32 bits so a single u64 sort orders by Hilbert // index while preserving the original position for tie-breaking. let packed = (u64::from(k) << 32) | (i as u64); indexed.push(packed); } indexed.sort_unstable(); let mut last_key: Option = None; for &packed in &*indexed { let key = (packed >> 32) as u32; let src_idx = (packed & 0xFFFF_FFFF) as usize; if last_key != Some(key) { #[expect( clippy::cast_possible_truncation, reason = "dict.len() <= coord_count <= u32::MAX" )] let slot = (dict_xy.len() / 2) as u32; dict_xy.push(vertices[src_idx * 2]); dict_xy.push(vertices[src_idx * 2 + 1]); remap.insert(key, slot); last_key = Some(key); } } for k in offsets.iter_mut() { *k = remap[k]; } } /// Push consecutive offset-differences from `offsets` onto `lengths`. /// /// Expects a slice of `n + 1` elements and produces `n` lengths, /// one per consecutive pair: `offsets[i + 1] - offsets[i]`. #[inline] fn extend_offsets(lengths: &mut Vec, offsets: &[u32]) -> usize { lengths.extend(offsets.windows(2).map(|w| w[1] - w[0])); offsets.len() - 1 } /// Convert geometry offsets to length stream for encoding. /// This is the inverse of `decode_root_length_stream`. /// /// The offset array can be either: /// - Sparse: entries only for geometries that need them (types > `buffer_id`), N+1 entries for N matching geoms /// - Dense (normalized): N+1 entries for N geometry types, indexed by geometry position /// /// If dense `(len == geom_types.len() + 1)`, use geometry index directly. /// If sparse, use sequential indexing for matching geometry types. fn encode_root_length_stream( geom_types: &[GeometryType], geom_offsets: &[u32], buffer_id: GeometryType, ) -> Vec { if geom_offsets.len() == geom_types.len() + 1 { // Dense: zip by position, then filter out non-contributing types. geom_types .iter() .zip(geom_offsets.windows(2)) .filter(|&(&t, _)| t > buffer_id) .map(|(_, w)| w[1] - w[0]) .collect() } else { // Sparse: filter types first, then zip with consecutive offset pairs. geom_types .iter() .filter(|&&t| t > buffer_id) .zip(geom_offsets.windows(2)) .map(|(_, w)| w[1] - w[0]) .collect() } } /// Convert part offsets to length stream for level 1 encoding. fn encode_level1_length_stream( geom_types: &[GeometryType], geom_offsets: &[u32], part_offsets: &[u32], is_line_string_present: bool, ) -> Vec { let mut lengths = Vec::new(); let mut part_idx = 0; for (i, &geom_type) in geom_types.iter().enumerate() { if geom_type.is_polygon() || (is_line_string_present && geom_type.is_linestring()) { let n = (geom_offsets[i + 1] - geom_offsets[i]).as_usize(); part_idx += extend_offsets(&mut lengths, &part_offsets[part_idx..=part_idx + n]); } // Note: Point/MultiPoint don't have entries in the sparse part_offsets used // at this call site, so part_idx must not advance for non-length types here. } lengths } /// Compute ring vertex-count lengths for the no-geometry-offsets + has-ring-offsets case. /// /// In this branch `part_offsets` is a **dense** N+1 array (one slot per geometry, /// including Points) and `ring_offsets` holds the vertex offsets for every slot. /// Using the geometry index directly as the ring-slot index avoids the /// running-counter misalignment that `encode_level1_length_stream` would produce /// when non-length types (Points) occupy slots that a sparse counter skips. fn encode_ring_lengths_for_mixed( geom_types: &[GeometryType], part_offsets: &[u32], ring_offsets: &[u32], has_line_string: bool, ) -> Vec { let mut lengths = Vec::new(); for (i, &geom_type) in geom_types.iter().enumerate() { if geom_type.is_polygon() || (has_line_string && geom_type.is_linestring()) { let s = part_offsets[i].as_usize(); let e = part_offsets[i + 1].as_usize(); extend_offsets(&mut lengths, &ring_offsets[s..=e]); } } lengths } /// Convert ring offsets to length stream for level 2 encoding. /// This is the inverse of `decode_level2_length_stream`. /// /// The `geom_offsets` array is expected to be an N+1 element array for N geometries. /// The `part_offsets` array tracks ring counts cumulatively. fn encode_level2_length_stream( geom_types: &[GeometryType], geom_offsets: &[u32], part_offsets: &[u32], ring_offsets: &[u32], ) -> Vec { let mut lengths = Vec::new(); let mut part_idx = 0; let mut ring_idx = 0; for (i, &geom_type) in geom_types.iter().enumerate() { let count = (geom_offsets[i + 1] - geom_offsets[i]).as_usize(); // Only Polygon and MultiPolygon have ring data in level 2 // LineStrings with Polygon present add their vertex counts directly to ring_offsets, // but they don't have parts (ring count per linestring is always 1 implicitly) if geom_type.is_polygon() { // Polygon/MultiPolygon: iterate through sub-polygons, each has parts (ring counts) for _ in 0..count { let n = (part_offsets[part_idx + 1] - part_offsets[part_idx]).as_usize(); ring_idx += extend_offsets(&mut lengths, &ring_offsets[ring_idx..=ring_idx + n]); part_idx += 1; } } else if geom_type.is_linestring() { // LineStrings contribute to ring_offsets directly (vertex counts) ring_idx += extend_offsets(&mut lengths, &ring_offsets[ring_idx..=ring_idx + count]); } // Note: Point/MultiPoint don't contribute to ring_offsets } lengths } /// Convert part offsets without ring buffer to length stream. /// /// This path is reached only when `ring_offsets` is absent, which means no Polygon/MultiPolygon /// types are present (they always create `ring_offsets`). Only LineString/MultiLineString /// contribute vertex-count lengths here; Point/MultiPoint use an implicit count of 1 in the /// decoder and produce no entry in this stream. fn encode_level1_without_ring_buffer_length_stream( geom_types: &[GeometryType], geom_offsets: &[u32], part_offsets: &[u32], ) -> Vec { let mut lengths = Vec::new(); let mut part_idx = 0; for (i, &geom_type) in geom_types.iter().enumerate() { if geom_type.is_linestring() { let n = (geom_offsets[i + 1] - geom_offsets[i]).as_usize(); part_idx += extend_offsets(&mut lengths, &part_offsets[part_idx..=part_idx + n]); } // Point/MultiPoint don't contribute to part_offsets; part_idx must not advance. } lengths } /// Normalize `geom_offsets` for mixed geometry types. fn normalize_geometry_offsets(vector_types: &[GeometryType], geom_offsets: &[u32]) -> Vec { let mut normalized = Vec::with_capacity(vector_types.len() + 1); let mut offset = 0_u32; let mut sparse_idx = 0_usize; // Index into sparse geom_offsets for &geom_type in vector_types { normalized.push(offset); if geom_type.is_multi() { // Multi* types get their count from the sparse array if sparse_idx + 1 < geom_offsets.len() { let start = geom_offsets[sparse_idx]; let end = geom_offsets[sparse_idx + 1]; offset += end - start; sparse_idx += 1; } } else { // Non-Multi types have implicit count of 1 offset += 1; } } normalized.push(offset); normalized } /// Normalize `part_offsets` for ring-based indexing (Polygon mixed with `Point`/`LineString`). /// /// Called only when `geom_offsets` is absent (no Multi\* types) and `ring_offsets` is /// present. In this context `part_offsets` is a compact polygon-only array; this function /// expands it to a dense per-geometry array so that `encode_ring_lengths_for_mixed` can index /// directly by geometry position. /// /// Each slot in the output holds the first index into `ring_offsets` for that geometry: /// - `Point`: no contribution — slot range is empty (`ring_idx` unchanged). /// - `LineString`: contributes 1 slot (vertex count) — slot range is 1. /// - `Polygon`: contributes `ring_count` slots — slot range equals its ring count. fn normalize_part_offsets_for_rings( vector_types: &[GeometryType], part_offsets: &[u32], ring_offsets: &[u32], ) -> Vec { let mut normalized = Vec::with_capacity(vector_types.len() + 1); let mut ring_idx = 0_u32; let mut part_idx = 0_usize; for &geom_type in vector_types { normalized.push(ring_idx); if geom_type == Point { // Point has no vertex-count slot in ring_offsets. } else if geom_type.is_linestring() { // Each LineString occupies exactly one slot in ring_offsets. ring_idx += 1; } else if geom_type.is_polygon() && part_idx + 1 < part_offsets.len() { // Polygon occupies ring_count slots (one vertex-count per ring). let ring_count = part_offsets[part_idx + 1] - part_offsets[part_idx]; ring_idx += ring_count; part_idx += 1; } // No Multi* types can appear here (they always produce geom_offsets). } // ring_idx must equal ring_offsets.len() - 1 for well-formed data. debug_assert_eq!( ring_idx as usize, ring_offsets.len().saturating_sub(1), "ring index mismatch after normalization" ); normalized.push(ring_idx); normalized } /// Whether to race dictionary-based vertex layouts (Hilbert, Morton) against /// the plain Vec2 layout for this geometry column. /// /// Profiling showed unconditional racing is ~2× slower overall: most layers /// have high vertex uniqueness, where the dict layouts cannot win and the /// extra sort + `HashMap` build is wasted. Gate on Morton fitting in 16 bits /// per axis (required by the spec) and on a `HyperLogLog`-estimated /// uniqueness ratio below the threshold. #[hotpath::measure] fn dict_may_be_beneficial(vertices: &[i32], enc: &Encoder) -> bool { const MAXIMUM_UNIQUENESS_THRESHOLD_FOR_DICT: f64 = 0.66; let coord_count = vertices.len() / 2; if coord_count == 0 || enc.morton_cache.is_none() { return false; } let mut hll = HyperLogLog::>::with_hasher(0.03, SipHasherBuilder::from_seed(0, 0)); for c in vertices.chunks_exact(2) { hll.insert(&Coord:: { x: c[0], y: c[1] }); } #[expect(clippy::cast_precision_loss)] let estimated_unique = hll.len().clamp(0.0, coord_count as f64); #[expect(clippy::cast_precision_loss)] let uniqueness_ratio = estimated_unique / coord_count as f64; uniqueness_ratio < MAXIMUM_UNIQUENESS_THRESHOLD_FOR_DICT } /// Pre-populated by [`StagedLayer::encode_into`](crate::encoder::StagedLayer::encode_into); /// callers must have gated on [`dict_may_be_beneficial`] which rejects layers /// whose extent does not fit Morton. fn get_morton(enc: &Encoder) -> Morton { enc.morton_cache.expect( "morton_cache populated by StagedLayer::encode_into; gated by dict_may_be_beneficial", ) } /// Pre-populated by [`StagedLayer::encode_into`](crate::encoder::StagedLayer::encode_into). fn get_hilbert_params(enc: &Encoder) -> CurveParams { enc.hilbert_cache .expect("hilbert_cache populated by StagedLayer::encode_into") } /// Encode the plain Vec2 vertex layout: componentwise-delta over the raw /// `[x0, y0, x1, y1, …]` slice. fn encode_vec2_vertex_stream( vertices: &[i32], enc: &mut Encoder, codecs: &mut Codecs, ) -> MltResult { let delta = encode_componentwise_delta_vec2s(vertices, &mut codecs.logical.u32_tmp); let ctx = StreamCtx::geom(StreamType::Data(DictionaryType::Vertex), "vertex"); let logical = LogicalEncoding::ComponentwiseDelta; write_geo_precomputed_stream(delta, ctx, logical, enc, &mut codecs.physical) } /// Encode a Morton-keyed vertex dictionary: per-vertex offsets stream /// followed by a delta-encoded Morton-code dictionary. fn encode_morton_vertex_streams( vertices: &[i32], enc: &mut Encoder, codecs: &mut Codecs, ) -> MltResult { let morton = get_morton(enc); let (dict, offsets) = build_morton_dict(vertices, morton)?; let mut n: u8 = 0; let ctx = StreamCtx::geom(StreamType::Offset(OffsetType::Vertex), "vertex_offsets"); n += write_geo_u32_stream(&offsets, ctx, enc, codecs)?; let delta = encode_morton_deltas(&dict, &mut codecs.logical.u32_tmp); let ctx = StreamCtx::geom(StreamType::Data(DictionaryType::Morton), "vertex"); let logical = LogicalEncoding::MortonDelta(morton); n += write_geo_precomputed_stream(delta, ctx, logical, enc, &mut codecs.physical)?; Ok(n) } /// Encode a Hilbert-keyed vertex dictionary: per-vertex offsets stream /// followed by a componentwise-delta-encoded `[x, y, …]` dictionary in /// Hilbert order. fn encode_hilbert_vertex_streams( vertices: &[i32], enc: &mut Encoder, codecs: &mut Codecs, ) -> MltResult { let params = get_hilbert_params(enc); let mut n: u8 = 0; // Take scratch ownership locally: `write_geo_*_stream` needs `&mut Codecs`, // which would otherwise conflict with our `&[..]` views into these slots. let mut offsets = mem::take(&mut codecs.logical.hilbert_offsets); let mut indexed = mem::take(&mut codecs.logical.hilbert_indexed); let mut dict_xy = mem::take(&mut codecs.logical.hilbert_dict_xy); let mut remap = mem::take(&mut codecs.logical.hilbert_remap); build_hilbert_dict( vertices, params, &mut offsets, &mut indexed, &mut dict_xy, &mut remap, ); // Done with these — restore so the physical-encoding race below can use // them via the codec. codecs.logical.hilbert_indexed = indexed; codecs.logical.hilbert_remap = remap; let ctx = StreamCtx::geom(StreamType::Offset(OffsetType::Vertex), "vertex_offsets"); n += write_geo_u32_stream(&offsets, ctx, enc, codecs)?; // Reuse `offsets` as the delta output rather than allocating another Vec; // also keeps `codecs.logical.u32_values` free for the inner race. encode_componentwise_delta_vec2s(&dict_xy, &mut offsets); let ctx = StreamCtx::geom(StreamType::Data(DictionaryType::Vertex), "vertex"); let logical = LogicalEncoding::ComponentwiseDelta; n += write_geo_precomputed_stream(&offsets, ctx, logical, enc, &mut codecs.physical)?; codecs.logical.hilbert_offsets = offsets; codecs.logical.hilbert_dict_xy = dict_xy; Ok(n) } /// Write a geometry `u32` stream: [`Encoder::override_int_enc`] when explicit mode is active, /// otherwise try all pruned candidates and keep the shortest. /// /// Returns `1` if the stream was written, `0` if it was skipped. Empty streams are skipped /// unless [`Encoder::force_stream`] returns `true` for this stream's [`StreamCtx`]. fn write_geo_u32_stream( data: &[u32], ctx: StreamCtx, enc: &mut Encoder, codecs: &mut Codecs, ) -> MltResult { Ok(if data.is_empty() && !enc.force_stream(&ctx) { 0 } else { codecs.write_int_stream(data, &ctx, enc)?; 1 }) } /// Like [`write_geo_u32_stream`] but for pre-logically-encoded data: competes /// only the physical encoders instead of applying a logical transform. /// /// Returns `1` if the stream was written, `0` if skipped (empty + no force). fn write_geo_precomputed_stream( data: &[u32], ctx: StreamCtx, logical: LogicalEncoding, enc: &mut Encoder, physical: &mut PhysicalCodecs, ) -> MltResult { use PhysicalEncoding as PE; Ok(if data.is_empty() && !enc.force_stream(&ctx) { 0 } else { if let Some(int_enc) = enc.override_int_enc(&ctx) { physical.write_encoded_as::<[u32]>(&ctx, enc, logical, data, int_enc.physical)?; } else if data.is_empty() { let meta = StreamMeta::new2(ctx.stream_type, logical, PE::None, 0)?; write_stream_payload(&mut enc.data, meta, false, &[])?; } else { let mut alt = enc.try_alternatives(); alt.with(|enc| { let vals = physical.fastpfor(data)?; let meta = StreamMeta::new2(ctx.stream_type, logical, PE::FastPFor256, data.len())?; write_stream_payload(&mut enc.data, meta, false, vals) })?; alt.with(|enc| { let vals = physical.varint(data); let meta = StreamMeta::new2(ctx.stream_type, logical, PE::VarInt, data.len())?; write_stream_payload(&mut enc.data, meta, false, vals) })?; } 1 }) } impl GeometryValues { /// Write the geometry column to `enc`. #[hotpath::measure] pub fn write_to(self, enc: &mut Encoder, codecs: &mut Codecs) -> MltResult<()> { let Self { vector_types, geometry_offsets, part_offsets, ring_offsets, index_buffer, triangles, vertices, } = self; // Flatten every Option → Vec (empty == not present). // triangles: None means no tessellation; Some([]) can't occur in practice (each // push_geom appends a count), so empty == absent is safe here too. // vertices: None means no coordinate data (e.g. empty layer). let geom_offsets = geometry_offsets.unwrap_or_default(); let part_offsets = part_offsets.unwrap_or_default(); let ring_offsets = ring_offsets.unwrap_or_default(); let index_buffer = index_buffer.unwrap_or_default(); let triangles = triangles.unwrap_or_default(); let vertices = vertices.unwrap_or_default(); // Direct callers (tests, custom drivers) skip `StagedLayer::encode_into` // and arrive with empty caches; populate from `vertices` so the // dictionary builders can rely on them unconditionally. if enc.hilbert_cache.is_none() { enc.hilbert_cache = Some(CurveParams::from_vertices(&vertices)); } if enc.morton_cache.is_none() { let p = enc.hilbert_cache.expect("populated above"); enc.morton_cache = Morton::new(p.bits, p.shift).ok(); } let meta: Vec = vector_types.iter().map(|t| *t as u32).collect(); let part_offsets = if geom_offsets.is_empty() && !ring_offsets.is_empty() && !part_offsets.is_empty() && part_offsets.len() != vector_types.len() + 1 { // Normalize part_offsets when there are no geometry offsets but ring offsets exist. normalize_part_offsets_for_rings(&vector_types, &part_offsets, &ring_offsets) } else { part_offsets }; // Write column type to meta; reserve exactly 1 byte for stream count // (geometry never exceeds ~8 streams, always fits in a single varint byte). enc.write_column_type(ColumnType::Geometry)?; let stream_count_pos = enc.data.len(); enc.data.push(0); // placeholder — patched below let mut n: u8 = 0; // Meta stream — always written, even for a zero-feature layer. let ctx = StreamCtx::geom(StreamType::Length(LengthType::VarBinary), "meta"); codecs.write_int_stream(&meta, &ctx, enc)?; n += 1; // Topology: compute each length stream and write it immediately. if !geom_offsets.is_empty() { let geom_offsets = if geom_offsets.len() == vector_types.len() + 1 { geom_offsets } else { normalize_geometry_offsets(&vector_types, &geom_offsets) }; let data = encode_root_length_stream(&vector_types, &geom_offsets, Polygon); let ctx = StreamCtx::geom(StreamType::Length(LengthType::Geometries), "geometries"); n += write_geo_u32_stream(&data, ctx, enc, codecs)?; // part_offsets is intentionally kept sparse here (polygon-only cumulative // ring counts). encode_level1/2_length_stream navigate it with a running // part_idx counter that advances only for Polygon/LineString types, which // matches the sparse layout. Densifying via normalize_part_offsets_for_rings // would insert Point slots and corrupt the counter arithmetic. if !part_offsets.is_empty() { if ring_offsets.is_empty() { // geom → parts only (no rings). let data = encode_level1_without_ring_buffer_length_stream( &vector_types, &geom_offsets, &part_offsets, ); let ctx = StreamCtx::geom(StreamType::Length(LengthType::Parts), "no_rings"); n += write_geo_u32_stream(&data, ctx, enc, codecs)?; } else { // Full topology: geom → parts → rings. // LineStrings contribute to rings here, not to parts. let data = encode_level1_length_stream( &vector_types, &geom_offsets, &part_offsets, false, ); let ctx = StreamCtx::geom(StreamType::Length(LengthType::Parts), "rings"); n += write_geo_u32_stream(&data, ctx, enc, codecs)?; let data = encode_level2_length_stream( &vector_types, &geom_offsets, &part_offsets, &ring_offsets, ); let ctx = StreamCtx::geom(StreamType::Length(LengthType::Rings), "rings2"); n += write_geo_u32_stream(&data, ctx, enc, codecs)?; } } } else if !part_offsets.is_empty() { if ring_offsets.is_empty() { let data = encode_root_length_stream(&vector_types, &part_offsets, Point); let ctx = StreamCtx::geom(StreamType::Length(LengthType::Parts), "no_rings"); n += write_geo_u32_stream(&data, ctx, enc, codecs)?; } else { // No Multi* types; parts → rings (Polygon / mixed Point+Polygon). // Java writes an empty GEOMETRIES stream here for tessellated polygons; only do // so when explicitly forced (e.g. to preserve byte-for-byte Java compatibility). let ctx = StreamCtx::geom(StreamType::Length(LengthType::Geometries), "geometries"); n += write_geo_u32_stream(&[], ctx, enc, codecs)?; let data = encode_root_length_stream(&vector_types, &part_offsets, LineString); let ctx = StreamCtx::geom(StreamType::Length(LengthType::Parts), "parts"); n += write_geo_u32_stream(&data, ctx, enc, codecs)?; // part_offs is a dense N+1 array (one slot per geometry incl. Points); // ring_offs stores vertex offsets per slot. The dense-aware helper skips // Point slots by index rather than a running counter. let has_line_string = vector_types .iter() .copied() .any(GeometryType::is_linestring); let data = encode_ring_lengths_for_mixed( &vector_types, &part_offsets, &ring_offsets, has_line_string, ); let ctx = StreamCtx::geom(StreamType::Length(LengthType::Rings), "parts_ring"); n += write_geo_u32_stream(&data, ctx, enc, codecs)?; } } let ctx = StreamCtx::geom(StreamType::Length(LengthType::Triangles), "triangles"); n += write_geo_u32_stream(&triangles, ctx, enc, codecs)?; let ctx = StreamCtx::geom(StreamType::Offset(OffsetType::Index), "triangles_indexes"); n += write_geo_u32_stream(&index_buffer, ctx, enc, codecs)?; if let Some(forced) = enc.override_vertex_buffer_type() { n += match forced { VertexBufferType::Vec2 => encode_vec2_vertex_stream(&vertices, enc, codecs)?, VertexBufferType::Morton => encode_morton_vertex_streams(&vertices, enc, codecs)?, VertexBufferType::Hilbert => encode_hilbert_vertex_streams(&vertices, enc, codecs)?, }; } else if dict_may_be_beneficial(&vertices, enc) { // Morton fits (the gate above ensures it), so race all three. let mut winner_size: usize = usize::MAX; let mut winner_stream_cnt: u8 = 0; let mut alt = enc.try_alternatives(); alt.with(|e| { let ds = e.data.len(); let ms = e.meta.len(); winner_stream_cnt = encode_vec2_vertex_stream(&vertices, e, codecs)?; winner_size = (e.data.len() - ds) + (e.meta.len() - ms); Ok(()) })?; alt.with(|e| { let ds = e.data.len(); let ms = e.meta.len(); let cnt = encode_hilbert_vertex_streams(&vertices, e, codecs)?; let size = (e.data.len() - ds) + (e.meta.len() - ms); if size < winner_size { winner_stream_cnt = cnt; winner_size = size; } Ok(()) })?; alt.with(|e| { let ds = e.data.len(); let ms = e.meta.len(); let cnt = encode_morton_vertex_streams(&vertices, e, codecs)?; let size = (e.data.len() - ds) + (e.meta.len() - ms); if size < winner_size { winner_stream_cnt = cnt; } Ok(()) })?; drop(alt); n += winner_stream_cnt; } else { n += encode_vec2_vertex_stream(&vertices, enc, codecs)?; } // Patch the reserved stream-count byte. debug_assert!(n <= 127, "geometry stream count must fit in one byte"); enc.data[stream_count_pos] = n; Ok(()) } } fn encode_morton_deltas<'a>(codes: &[u32], buffer: &'a mut Vec) -> &'a mut Vec { buffer.clear(); if let Some(&first) = codes.first() { buffer.reserve(codes.len()); buffer.extend(std::iter::once(first).chain(codes.windows(2).map(|w| w[1] - w[0]))); } buffer } #[cfg(test)] mod tests { use super::*; #[test] fn test_build_morton_dict() { let meta = Morton { bits: 4, shift: 0 }; // vertices: [x0,y0, x1,y1, x2,y2, x3,y3] — repeat (1,2) to test dedup let vertices = [1, 2, 3, 4, 1, 2, 0, 0]; let (dict, offsets) = build_morton_dict(&vertices, meta).unwrap(); assert!( dict.windows(2).all(|w| w[0] < w[1]), "dict not sorted/unique" ); assert_eq!(offsets.len(), 4, "offsets length == number of vertex pairs"); assert_eq!(offsets[0], offsets[2], "duplicate (1,2) should share index"); assert!(offsets.iter().all(|&o| (o as usize) < dict.len())); } #[test] fn test_encode_root_length_stream() { // Single Polygon geometry (no Multi) let types = vec![Polygon]; let offsets = vec![0, 1]; // One polygon let lengths = encode_root_length_stream(&types, &offsets, Polygon); // Polygon == buffer_id, so no length encoded assert!(lengths.is_empty()); // MultiPolygon needs length encoded let types = vec![GeometryType::MultiPolygon]; let offsets = vec![0, 2]; // MultiPolygon with 2 polygons let lengths = encode_root_length_stream(&types, &offsets, Polygon); assert_eq!(lengths, vec![2]); } } ================================================ FILE: rust/mlt-core/src/encoder/geometry/geotype.rs ================================================ use geo::{Convert as _, TriangulateEarcut as _}; use geo_types::{Coord, Geometry, LineString, MultiLineString, MultiPoint, MultiPolygon, Polygon}; use crate::decoder::{GeometryType, GeometryValues}; impl TryFrom<&Geometry> for GeometryType { type Error = (); fn try_from(geom: &Geometry) -> Result { Ok(match geom { Geometry::::Point(_) => Self::Point, Geometry::::MultiPoint(_) => Self::MultiPoint, Geometry::::LineString(_) => Self::LineString, Geometry::::MultiLineString(_) => Self::MultiLineString, Geometry::::Polygon(_) => Self::Polygon, Geometry::::MultiPolygon(_) => Self::MultiPolygon, Geometry::::Line(_) | Geometry::::GeometryCollection(_) | Geometry::::Rect(_) | Geometry::::Triangle(_) => { return Err(()); } }) } } /// Run the Earcut algorithm on `polygon`, append triangle indices (shifted by `vertex_offset`) /// into `index_buf`, and return `(num_triangles, num_vertices)`. fn earcut_into(polygon: &Polygon, vertex_offset: u32, index_buf: &mut Vec) -> (u32, u32) { let polygon_f64: Polygon = polygon.convert(); let raw = polygon_f64.earcut_triangles_raw(); let num_triangles = u32::try_from(raw.triangle_indices.len() / 3).expect("too many triangles"); let num_vertices = u32::try_from(raw.vertices.len()).expect("too many vertices"); for i in raw.triangle_indices { let base = u32::try_from(i).expect("mlt vertex index overflow"); let idx = base .checked_add(vertex_offset) .expect("vertex index overflow"); index_buf.push(idx); } (num_triangles, num_vertices) } impl GeometryValues { /// Returns a [`GeometryValues`] with an empty `triangles` buffer pre-initialized. /// /// When `triangles` is `Some`, polygon push methods automatically compute and store /// Earcut tessellation data as geometries are added. /// Use [`Self::default`] when tessellation is not required. #[must_use] pub fn new_tessellated() -> Self { Self { triangles: Some(vec![]), ..Default::default() } } /// Tessellate `polygon` using the Earcut algorithm and append the results directly into /// `self.index_buffer` and `self.triangles`. fn tessellate_polygon(&mut self, polygon: &Polygon) { if let Some(triangles) = self.triangles.as_mut() { let (num_triangles, _) = earcut_into(polygon, 0, self.index_buffer.get_or_insert_with(Vec::new)); triangles.push(num_triangles); } } /// Tessellate all polygons in `mp` and append the combined results into /// `self.index_buffer` and `self.triangles`. /// /// Indices for each constituent polygon are offset by the cumulative vertex count of all /// preceding polygons so they reference the correct positions in the shared vertex buffer. /// A single total triangle count (summed over all constituent polygons) is pushed into /// `self.triangles`. fn tessellate_multi_polygon(&mut self, mp: &MultiPolygon) { if let Some(triangles) = self.triangles.as_mut() { let mut total_triangles = 0u32; let mut vertex_offset = 0u32; let index_buffer = self.index_buffer.get_or_insert_with(Vec::new); for poly in &mp.0 { let (num_triangles, num_verts) = earcut_into(poly, vertex_offset, index_buffer); total_triangles += num_triangles; vertex_offset += num_verts; } triangles.push(total_triangles); } } /// Add a geometry to this decoded geometry collection. /// This is the reverse of `to_geojson` - it converts a `&Geometry` /// into the internal MLT representation with offset arrays. #[must_use] pub fn with_geom(mut self, geom: &Geometry) -> Self { self.push_geom(geom); self } /// Add a geometry to this decoded geometry collection (mutable version). pub fn push_geom(&mut self, geom: &Geometry) { match geom { Geometry::::Point(p) => self.push_point(p.0), Geometry::::Line(l) => self.push_linestring(&LineString(vec![l.start, l.end])), Geometry::::LineString(ls) => self.push_linestring(ls), Geometry::::Polygon(p) => self.push_polygon(p), Geometry::::MultiPoint(mp) => self.push_multi_point(mp), Geometry::::MultiLineString(mls) => self.push_multi_linestring(mls), Geometry::::MultiPolygon(mp) => self.push_multi_polygon(mp), Geometry::::Triangle(t) => self.push_polygon(&t.to_polygon()), Geometry::::Rect(r) => self.push_polygon(&r.to_polygon()), Geometry::::GeometryCollection(gc) => { for g in gc { self.push_geom(g); } } } } fn push_point(&mut self, coord: Coord) { self.vector_types.push(GeometryType::Point); self.vertices .get_or_insert_with(Vec::new) .extend([coord.x, coord.y]); } fn push_linestring(&mut self, ls: &LineString) { self.vector_types.push(GeometryType::LineString); let verts = self.vertices.get_or_insert_with(Vec::new); // If ring_offsets exists (i.e., there's a Polygon in the layer), // add LineString vertex count to ring_offsets instead of part_offsets. // This matches Java's behavior where LineString adds to numRings when containsPolygon. let offsets = self .ring_offsets .as_mut() .unwrap_or_else(|| self.part_offsets.get_or_insert_with(Vec::new)); push_linestrings(std::iter::once(ls), verts, offsets); } fn push_polygon(&mut self, poly: &Polygon) { // Only on the very first polygon: if LineStrings were pushed before us, // their vertex offsets are sitting in part_offsets. Move them to // ring_offsets now, before we set up ring_offsets for polygon use. // On subsequent polygons ring_offsets is already initialized and // part_offsets holds polygon ring-range data — leave both alone. self.vector_types.push(GeometryType::Polygon); self.init_polygon_offsets(); let verts = self.vertices.get_or_insert_with(Vec::new); let rings = self.ring_offsets.as_mut().unwrap(); let parts = self.part_offsets.as_mut().unwrap(); push_polygon_rings(poly, verts, rings, parts); self.tessellate_polygon(poly); } /// Initialize offset arrays for polygon storage. On the first polygon, /// moves any `LineString` vertex offsets from `part_offsets` to `ring_offsets`. fn init_polygon_offsets(&mut self) { if self.ring_offsets.is_none() && let Some(ls_parts) = self.part_offsets.take() { self.ring_offsets = Some(ls_parts); } init_offsets(self.ring_offsets.get_or_insert_with(Vec::new)); init_offsets(self.part_offsets.get_or_insert_with(Vec::new)); } fn push_multi_point(&mut self, mp: &MultiPoint) { self.vector_types.push(GeometryType::MultiPoint); let verts = self.vertices.get_or_insert_with(Vec::new); for point in mp { verts.extend([point.0.x, point.0.y]); } self.push_geometry_count(u32::try_from(mp.0.len()).expect("point count overflow")); } fn push_multi_linestring(&mut self, mls: &MultiLineString) { self.vector_types.push(GeometryType::MultiLineString); let verts = self.vertices.get_or_insert_with(Vec::new); // When a Polygon is present (ring_offsets exists), LineString vertex counts // go to ring_offsets instead of part_offsets. This matches Java's behavior. let offsets = self .ring_offsets .as_mut() .unwrap_or_else(|| self.part_offsets.get_or_insert_with(Vec::new)); push_linestrings(mls.iter(), verts, offsets); self.push_geometry_count(u32::try_from(mls.0.len()).expect("linestring count overflow")); } fn push_multi_polygon(&mut self, mp: &MultiPolygon) { self.vector_types.push(GeometryType::MultiPolygon); self.init_polygon_offsets(); let verts = self.vertices.get_or_insert_with(Vec::new); let rings = self.ring_offsets.as_mut().unwrap(); let parts = self.part_offsets.as_mut().unwrap(); for poly in mp { push_polygon_rings(poly, verts, rings, parts); } self.push_geometry_count(u32::try_from(mp.0.len()).expect("polygon count overflow")); self.tessellate_multi_polygon(mp); } /// Initialize and update `geometry_offsets` with a sub-geometry count. fn push_geometry_count(&mut self, count: u32) { let g = self.geometry_offsets.get_or_insert_with(Vec::new); init_offsets(g); g.push(g.last().unwrap() + count); } } /// Ensure offset array starts with 0. fn init_offsets(v: &mut Vec) { if v.is_empty() { v.push(0); } } /// Push a single polygon's rings (exterior + interiors) to the offset arrays. /// MLT omits closing vertices, so we strip them if present. fn push_polygon_rings( poly: &Polygon, verts: &mut Vec, rings: &mut Vec, parts: &mut Vec, ) { let mut ring_count = *parts.last().unwrap(); for ring in std::iter::once(poly.exterior()).chain(poly.interiors()) { push_ring(ring, verts, rings); ring_count += 1; } parts.push(ring_count); } /// Push a ring's coordinates (stripping closing vertex) to verts and update rings offset. fn push_ring(ring: &LineString, verts: &mut Vec, rings: &mut Vec) { let coords = &ring.0; let len = if coords.len() > 1 && coords.last() == coords.first() { coords.len() - 1 } else { coords.len() }; for c in &coords[..len] { verts.extend([c.x, c.y]); } let prev = *rings.last().unwrap(); rings.push(prev + u32::try_from(len).expect("vertex count overflow")); } /// Push linestrings to vertex buffer and offset array. fn push_linestrings<'a>( iter: impl Iterator>, verts: &mut Vec, offsets: &mut Vec, ) { init_offsets(offsets); for ls in iter { for c in ls.coords() { verts.extend([c.x, c.y]); } let prev = *offsets.last().unwrap(); offsets.push(prev + u32::try_from(ls.0.len()).expect("vertex count overflow")); } } #[cfg(test)] mod tests { use geo_types::{LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, wkt}; use insta::assert_snapshot; use integer_encoding::VarInt; use proptest::prelude::*; use super::*; use crate::__private::PhysicalEncoding; use crate::LazyParsed; use crate::decoder::{ DictionaryType, IntEncoding, LengthType, LogicalEncoding, Morton, OffsetType, RawGeometry, StreamMeta, StreamType, }; use crate::encoder::model::StreamCtx; use crate::encoder::{Codecs, EncodedStream, Encoder, ExplicitEncoder, IntEncoder}; use crate::test_helpers::{assert_empty, dec, parser}; use crate::utils::BinarySerializer as _; /// Encode, serialize, parse, and decode a `GeometryValues`. /// The input must already be in the dense canonical form that `from_encoded` /// produces (i.e. built via a previous `roundtrip` call, not via `push_*`). fn roundtrip(decoded: &GeometryValues) -> GeometryValues { let mut enc = Encoder::default(); let mut codecs = Codecs::default(); decoded .clone() .write_to(&mut enc, &mut codecs) .expect("Failed to encode"); let parsed = assert_empty(RawGeometry::from_bytes(&enc.data, &mut parser())); LazyParsed::Raw(parsed) .into_parsed(&mut dec()) .expect("Failed to decode") } /// Build a `GeometryValues` from a sequence of `geo_types::Geometry::` values via /// `push_geom` and perform a two-cycle encode/decode: /// /// 1. push -> encode -> decode (`canonical`): exercises `push_geom` and /// `normalize_geometry_offsets`; normalizes the sparse push_* layout to /// the dense form that `from_encoded` always returns. /// 2. canonical -> encode -> decode (`output`): verifies idempotency of /// encode/decode on the canonical form /// /// Comparing `canonical == output` catches both panics in the push path /// and silent data corruption in encode/decode fn roundtrip_via_push(geoms: &[Geometry]) -> (GeometryValues, GeometryValues) { let mut pushed = GeometryValues::default(); for g in geoms { pushed.push_geom(g); } let canonical = roundtrip(&pushed); let output = roundtrip(&canonical); (canonical, output) } fn arb_coord() -> impl Strategy> { (any::(), any::()).prop_map(|(x, y)| Coord:: { x, y }) } fn arb_geom() -> impl Strategy> { prop_oneof![ // Point arb_coord().prop_map(Point).prop_map(Geometry::::Point), // LineString prop::collection::vec(arb_coord(), 2..10) .prop_map(|coords| Geometry::::LineString(LineString(coords))), // Polygon (single exterior ring, no holes) prop::collection::vec(arb_coord(), 3..8).prop_map(|mut coords| { coords.push(coords[0]); Geometry::::Polygon(Polygon::new(LineString(coords), vec![])) }), // MultiPoint prop::collection::vec(arb_coord(), 2..8).prop_map(|coords| { Geometry::::MultiPoint(MultiPoint(coords.into_iter().map(Point).collect())) }), // MultiLineString prop::collection::vec(prop::collection::vec(arb_coord(), 2..6), 2..5,).prop_map( |lines| Geometry::::MultiLineString(MultiLineString( lines.into_iter().map(LineString).collect(), )) ), // MultiPolygon prop::collection::vec(arb_coord(), 3..6).prop_map(|mut coords| { coords.push(coords[0]); Geometry::::MultiPolygon(MultiPolygon(vec![Polygon::new( LineString(coords), vec![], )])) }), ] } /// Mixing `LineString` with `MultiLineString` fn arb_mixed_linestring_geoms() -> impl Strategy>> { prop::collection::vec(arb_geom(), 2..12) .prop_map(|geoms| { geoms .into_iter() .filter(|g| { matches!( g, Geometry::::LineString(_) | Geometry::::MultiLineString(_) ) }) .collect::>() }) .prop_filter("needs both LS and MLS", |geoms| { geoms .iter() .any(|g| matches!(g, Geometry::::LineString(_))) && geoms .iter() .any(|g| matches!(g, Geometry::::MultiLineString(_))) }) } /// Mixing `Point` with `MultiPoint` fn arb_mixed_point_geoms() -> impl Strategy>> { prop::collection::vec(arb_geom(), 2..12) .prop_map(|geoms| { geoms .into_iter() .filter(|g| { matches!( g, Geometry::::Point(_) | Geometry::::MultiPoint(_) ) }) .collect::>() }) .prop_filter("needs both P and MP", |geoms| { geoms.iter().any(|g| matches!(g, Geometry::::Point(_))) && geoms .iter() .any(|g| matches!(g, Geometry::::MultiPoint(_))) }) } /// Mixing `Polygon` with `MultiPolygon` fn arb_mixed_polygon_geoms() -> impl Strategy>> { prop::collection::vec(arb_geom(), 2..8) .prop_map(|geoms| { geoms .into_iter() .filter(|g| { matches!( g, Geometry::::Polygon(_) | Geometry::::MultiPolygon(_) ) }) .collect::>() }) .prop_filter("needs both Poly and MPoly", |geoms| { geoms .iter() .any(|g| matches!(g, Geometry::::Polygon(_))) && geoms .iter() .any(|g| matches!(g, Geometry::::MultiPolygon(_))) }) } /// Mixing `Point` with `MultiLineString` fn arb_cross_point_mls_geoms() -> impl Strategy>> { prop::collection::vec( prop_oneof![ arb_coord().prop_map(Point).prop_map(Geometry::::Point), prop::collection::vec(prop::collection::vec(arb_coord(), 2..6), 2..5).prop_map( |lines| { Geometry::::MultiLineString(MultiLineString( lines.into_iter().map(LineString).collect(), )) } ), ], 2..12, ) .prop_filter("needs both Point and MultiLineString", |geoms| { geoms.iter().any(|g| matches!(g, Geometry::::Point(_))) && geoms .iter() .any(|g| matches!(g, Geometry::::MultiLineString(_))) }) } /// Mixing `Point` with `MultiPolygon`. fn arb_cross_point_mpoly_geoms() -> impl Strategy>> { prop::collection::vec( prop_oneof![ arb_coord().prop_map(Point).prop_map(Geometry::::Point), prop::collection::vec(arb_coord(), 3..6).prop_map(|mut coords| { coords.push(coords[0]); Geometry::::MultiPolygon(MultiPolygon(vec![Polygon::new( LineString(coords), vec![], )])) }), ], 2..10, ) .prop_filter("needs both Point and MultiPolygon", |geoms| { geoms.iter().any(|g| matches!(g, Geometry::::Point(_))) && geoms .iter() .any(|g| matches!(g, Geometry::::MultiPolygon(_))) }) } /// Mixing `LineString` with `MultiPolygon` fn arb_cross_ls_mpoly_geoms() -> impl Strategy>> { prop::collection::vec( prop_oneof![ prop::collection::vec(arb_coord(), 2..8) .prop_map(|coords| Geometry::::LineString(LineString(coords))), prop::collection::vec(arb_coord(), 3..6).prop_map(|mut coords| { coords.push(coords[0]); Geometry::::MultiPolygon(MultiPolygon(vec![Polygon::new( LineString(coords), vec![], )])) }), ], 2..10, ) .prop_filter("needs both LineString and MultiPolygon", |geoms| { geoms .iter() .any(|g| matches!(g, Geometry::::LineString(_))) && geoms .iter() .any(|g| matches!(g, Geometry::::MultiPolygon(_))) }) } proptest! { #[test] fn test_geometry_roundtrip(geom in arb_geom()) { let (canonical, output) = roundtrip_via_push(&[geom]); prop_assert_eq!(output, canonical); } #[test] fn test_mixed_linestring_roundtrip(geoms in arb_mixed_linestring_geoms()) { let (canonical, output) = roundtrip_via_push(&geoms); prop_assert_eq!(output, canonical); } #[test] fn test_mixed_point_roundtrip(geoms in arb_mixed_point_geoms()) { let (canonical, output) = roundtrip_via_push(&geoms); prop_assert_eq!(output, canonical); } #[test] fn test_mixed_polygon_roundtrip(geoms in arb_mixed_polygon_geoms()) { let (canonical, output) = roundtrip_via_push(&geoms); prop_assert_eq!(output, canonical); } #[ignore = "encoder does not implement this correctly"] #[test] fn test_cross_point_mls_roundtrip(geoms in arb_cross_point_mls_geoms()) { let (canonical, output) = roundtrip_via_push(&geoms); prop_assert_eq!(output, canonical); } #[ignore = "encoder does not implement this correctly"] #[test] fn test_cross_point_mpoly_roundtrip(geoms in arb_cross_point_mpoly_geoms()) { let (canonical, output) = roundtrip_via_push(&geoms); prop_assert_eq!(output, canonical); } #[test] fn test_cross_ls_mpoly_roundtrip(geoms in arb_cross_ls_mpoly_geoms()) { let (canonical, output) = roundtrip_via_push(&geoms); prop_assert_eq!(output, canonical); } } /// Verifies that a Morton-encoded vertex dictionary is fully expanded inside `from_encoded`. /// This ensures `GeometryValues` always holds flat `(x, y)` pairs. #[test] fn test_morton_vertex_dictionary_expansion() { use integer_encoding::VarIntWriter as _; // Morton vertex dictionary: 3 unique entries. // Raw codes [0, 16, 32] -> delta-encoded as [0, 16, 16]. // The MortonDelta logical encoding means the decoder will undo the delta, // then decode each Morton code to an (x, y) pair. let mut raw_bytes = vec![]; let mut buf = [0u8; 10]; for &v in &[0_u64, 16, 16] { let n = v.encode_var(&mut buf); raw_bytes.extend_from_slice(&buf[..n]); } let morton_dict = EncodedStream { meta: StreamMeta::new( StreamType::Data(DictionaryType::Morton), IntEncoding::new( LogicalEncoding::MortonDelta(Morton { bits: 3, shift: 0 }), PhysicalEncoding::VarInt, ), 3, // 3 dictionary entries -> 3 physical u32 values ), data: raw_bytes, }; // Assemble, serialize, parse, decode — same wire layout as geometry encoder: // stream count, then meta (geom type), parts, vertex offsets, Morton dict. let mut codecs = Codecs::default(); let mut enc = Encoder::with_explicit( Encoder::default().cfg, ExplicitEncoder::all(IntEncoder::varint()), ); enc.write_varint(4u32).unwrap(); codecs .write_int_stream( &[GeometryType::LineString as u32], &StreamCtx::geom(StreamType::Length(LengthType::VarBinary), "meta"), &mut enc, ) .unwrap(); codecs .write_int_stream( &[4u32], &StreamCtx::geom(StreamType::Length(LengthType::Parts), "parts"), &mut enc, ) .unwrap(); codecs .write_int_stream( &[0u32, 1, 2, 1], &StreamCtx::geom(StreamType::Offset(OffsetType::Vertex), "vertex"), &mut enc, ) .unwrap(); enc.write_stream(&morton_dict).unwrap(); let buffer = enc.data; let mut p = parser(); let parsed = assert_empty(RawGeometry::from_bytes(&buffer, &mut p)); assert_snapshot!(p.reserved(), @"72"); let mut d = dec(); let decoded = LazyParsed::Raw(parsed).into_parsed(&mut d).unwrap(); assert_snapshot!(d.consumed(), @"100"); assert_eq!(decoded.vertices, Some(vec![0i32, 0, 4, 0, 0, 4, 4, 0])); let geom = decoded.to_geojson(0).unwrap(); assert_eq!(geom, wkt!(LINESTRING(0 0,4 0,0 4,4 0)).into()); } mod tessellation_tests { use geo_types::{Geometry, LineString, MultiPolygon, Polygon}; use crate::decoder::GeometryValues; #[test] fn earcut_polygon_indices_in_range() { let exterior = LineString::from(vec![(0_i32, 0), (10, 0), (10, 10), (0, 10), (0, 0)]); let polygon = Polygon::new(exterior, vec![]); let mut g = GeometryValues::new_tessellated(); g.push_geom(&Geometry::::Polygon(polygon)); let tris = g.triangles().expect("triangles"); let n = tris[0]; assert!(n > 0, "expected at least one triangle"); let ib = g.index_buffer().expect("index buffer"); assert_eq!(ib.len(), usize::try_from(n).unwrap() * 3); // 4 unique (non-closing) vertices → indices in 0..4 assert!(ib.iter().all(|&i| i < 4)); } #[test] fn earcut_vertex_offset_for_multi_polygon_parts() { let exterior1 = LineString::from(vec![(0_i32, 0), (10, 0), (10, 10), (0, 10), (0, 0)]); let poly1 = Polygon::new(exterior1, vec![]); let exterior2 = LineString::from(vec![(20, 0), (30, 0), (30, 10), (20, 10), (20, 0)]); let poly2 = Polygon::new(exterior2, vec![]); let mut g = GeometryValues::new_tessellated(); g.push_geom(&Geometry::::MultiPolygon(MultiPolygon(vec![ poly1, poly2, ]))); let ib = g.index_buffer().expect("index buffer"); let tris = g.triangles().expect("triangles"); assert_eq!(tris.len(), 1); let total = usize::try_from(tris[0]).unwrap(); assert_eq!(ib.len(), total * 3); // First quad: 4 verts → 2 triangles, 6 indices let split = 6; let (first, second) = ib.split_at(split); assert!( first.iter().all(|&i| i < 4), "first polygon indices should reference verts 0..4: {first:?}" ); assert!( second.iter().all(|&i| (4..8).contains(&i)), "second polygon indices should reference verts 4..8: {second:?}" ); } } } ================================================ FILE: rust/mlt-core/src/encoder/geometry/mod.rs ================================================ pub(crate) mod encode; mod geotype; mod model; #[cfg(test)] mod tests; pub use model::*; ================================================ FILE: rust/mlt-core/src/encoder/geometry/model.rs ================================================ /// Describes how the vertex buffer should be encoded. #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] #[cfg_attr(test, derive(proptest_derive::Arbitrary))] #[cfg_attr(all(not(test), feature = "arbitrary"), derive(arbitrary::Arbitrary))] pub enum VertexBufferType { /// Standard 2D `(x, y)` pairs encoded with componentwise delta. #[default] Vec2, /// Morton (Z-order) dictionary encoding: /// Unique vertices are sorted by their Morton code and stored once. /// Each vertex position in the stream is replaced by its index into that dictionary. Morton, /// Hilbert curve dictionary encoding: /// Unique vertices are sorted by their Hilbert curve index and stored once, /// componentwise-delta encoded. Each vertex position in the stream is replaced /// by its index into that dictionary. Hilbert, } ================================================ FILE: rust/mlt-core/src/encoder/geometry/tests.rs ================================================ use std::collections::HashSet; use geo_types::{Coord, Geometry, LineString, Point, Polygon, point, wkt}; use pretty_assertions::assert_eq; use rstest::rstest; use crate::decoder::RawGeometry; use crate::encoder::model::EncoderConfig; use crate::encoder::{Codecs, Encoder, ExplicitEncoder, IntEncoder, VertexBufferType}; use crate::test_helpers::{assert_empty, dec, parser}; use crate::{Decode as _, DictionaryType, GeometryValues, LengthType, StreamType}; #[rstest] #[case::single_point(push_geoms(&[wkt!(POINT(10 20)).into()]))] #[case::linestring(push_geoms(&[wkt!(LINESTRING(10 20, 30 40, 50 60)).into()]))] #[case::polygon(push_geoms(&[wkt!(POLYGON((0 0, 100 0, 100 100, 0 100, 0 0))).into()]))] #[case::multi_polygon(push_geoms(&[wkt!(MULTIPOLYGON(((0 0, 10 0, 10 10, 0 0),(5 5, 15 5, 15 15, 5 15)))).into()]))] fn automatic_optimization_roundtrip(#[case] decoded: GeometryValues) { let mut enc = Encoder::default(); let mut codecs = Codecs::default(); decoded .clone() .write_to(&mut enc, &mut codecs) .expect("optimize failed"); assert_geometry_roundtrip(&enc.data, &decoded); } fn auto_mode_streams(decoded: &GeometryValues) -> Vec { let mut enc = Encoder::default(); let mut codecs = Codecs::default(); decoded .clone() .write_to(&mut enc, &mut codecs) .expect("encode failed"); let mut stream_types: Vec = encoded_stream_types(&enc.data).into_iter().collect(); stream_types.sort(); assert_geometry_roundtrip(&enc.data, decoded); stream_types } #[test] fn automatic_optimization_distinct_points_picks_vec2() { let decoded = push_geoms( &(0i32..10) .map(|i| point! { x: i, y: i }.into()) .collect::>(), ); insta::assert_debug_snapshot!(auto_mode_streams(&decoded), @r" [ Data( Vertex, ), Length( VarBinary, ), ] "); } #[test] fn automatic_optimization_repeated_points_picks_dict() { // The Hilbert vs. Morton race resolves deterministically for this input — // Hilbert wins, so the encoded streams use `Data(Vertex)` + a vertex // offset stream. The snapshot pins that outcome; if the race tie-break // or the heuristic ever changes it should fail loudly. let decoded = push_geoms(&std::iter::repeat_n(point! { x: 5, y: 5 }.into(), 20).collect::>()); insta::assert_debug_snapshot!(auto_mode_streams(&decoded), @r" [ Data( Vertex, ), Offset( Vertex, ), Length( VarBinary, ), ] "); } #[test] fn encoded_output_always_has_meta_stream() { let decoded = push_geoms(&[Geometry::::Point(Point(Coord:: { x: 1, y: 1 }))]); let mut enc = Encoder::default(); let mut codecs = Codecs::default(); decoded .write_to(&mut enc, &mut codecs) .expect("encode failed"); let raw = assert_empty(RawGeometry::from_bytes(&enc.data, &mut parser())); assert_eq!( raw.meta.meta.stream_type, StreamType::Length(LengthType::VarBinary), "meta (VarBinary) stream must always be present" ); } #[test] fn encoded_polygon_has_topology_streams() { let coords: Vec> = [(0, 0), (10, 0), (10, 10), (0, 0)] .into_iter() .map(|(x, y)| Coord:: { x, y }) .collect(); let decoded = push_geoms(&[Geometry::::Polygon(Polygon::new( LineString(coords), vec![], ))]); let mut enc = Encoder::default(); let mut codecs = Codecs::default(); decoded .write_to(&mut enc, &mut codecs) .expect("encode failed"); let stream_types = encoded_stream_types(&enc.data); assert!( stream_types.contains(&StreamType::Length(LengthType::Rings)) || stream_types.contains(&StreamType::Length(LengthType::Parts)), "polygon must produce at least a Parts or Rings length stream" ); } /// Encode `decoded` with the vertex layout pinned to `strategy`, return the /// (sorted) stream types in the wire output, and assert the bytes round-trip /// back to the same `GeometryValues`. fn forced_vertex_strategy_streams( decoded: &GeometryValues, strategy: VertexBufferType, ) -> Vec { let explicit = ExplicitEncoder { vertex_buffer_type: strategy, ..ExplicitEncoder::all(IntEncoder::varint()) }; let mut enc = Encoder::with_explicit(EncoderConfig::default(), explicit); let mut codecs = Codecs::default(); decoded .clone() .write_to(&mut enc, &mut codecs) .expect("encode failed"); let mut stream_types: Vec = encoded_stream_types(&enc.data).into_iter().collect(); stream_types.sort(); assert_geometry_roundtrip(&enc.data, decoded); stream_types } /// Multipoint with repeated coordinates so dict paths actually dedup. fn repeated_multipoint() -> GeometryValues { let mut g = GeometryValues::default(); g.push_geom(&wkt!(MULTIPOINT(5 5, 10 10, 5 5, 10 10, 0 0, 5 5)).into()); g } #[test] fn forced_vec2_streams() { let streams = forced_vertex_strategy_streams(&repeated_multipoint(), VertexBufferType::Vec2); insta::assert_debug_snapshot!(streams, @r" [ Data( Vertex, ), Length( VarBinary, ), Length( Geometries, ), ] "); } #[test] fn forced_morton_streams() { let streams = forced_vertex_strategy_streams(&repeated_multipoint(), VertexBufferType::Morton); insta::assert_debug_snapshot!(streams, @r" [ Data( Morton, ), Offset( Vertex, ), Length( VarBinary, ), Length( Geometries, ), ] "); } #[test] fn forced_hilbert_streams() { let streams = forced_vertex_strategy_streams(&repeated_multipoint(), VertexBufferType::Hilbert); insta::assert_debug_snapshot!(streams, @r" [ Data( Vertex, ), Offset( Vertex, ), Length( VarBinary, ), Length( Geometries, ), ] "); } #[test] fn manual_encode_works() { let decoded = push_geoms(&[wkt!(POINT(10 20)).into()]); let mut enc = Encoder::default(); let mut codecs = Codecs::default(); decoded .clone() .write_to(&mut enc, &mut codecs) .expect("encode failed"); let types = encoded_stream_types(&enc.data); assert!(types.contains(&StreamType::Data(DictionaryType::Vertex))); assert_geometry_roundtrip(&enc.data, &decoded); } /// Round-trip geometry bytes: parse then decode and compare. fn assert_geometry_roundtrip(data: &[u8], expected: &GeometryValues) { let mut p = parser(); let mut d = dec(); let raw = assert_empty(RawGeometry::from_bytes(data, &mut p)); let result = raw.decode(&mut d).unwrap(); assert!( d.consumed() > 0, "decoder should consume bytes after decode" ); assert_eq!(expected, &result); } fn push_geoms(geoms: &[Geometry]) -> GeometryValues { let mut d = GeometryValues::default(); for g in geoms { d.push_geom(g); } d } /// Collect all stream types present in the encoded geometry bytes (meta + items). fn encoded_stream_types(data: &[u8]) -> HashSet { let raw = assert_empty(RawGeometry::from_bytes(data, &mut parser())); std::iter::once(raw.meta.meta.stream_type) .chain(raw.items.iter().map(|s| s.meta.stream_type)) .collect() } ================================================ FILE: rust/mlt-core/src/encoder/id/mod.rs ================================================ mod staged_id; pub use staged_id::StagedId; ================================================ FILE: rust/mlt-core/src/encoder/id/staged_id.rs ================================================ use crate::MltResult; use crate::decoder::ColumnType; use crate::encoder::optimizer::{Presence, PropertyStats}; use crate::encoder::{Codecs, Encoder, StagedOptScalar, StagedScalar}; /// Staged ID column (encode-side, fully owned). /// /// Mirrors the `StagedProperty` enum pattern but without a column name: the /// staged value itself carries the eventual ID column width and optionality. #[derive(Debug, Clone, PartialEq)] pub enum StagedId { None, U32(StagedScalar), OptU32(StagedOptScalar), U64(StagedScalar), OptU64(StagedOptScalar), } impl StagedId { /// Construct from a sparse `Vec>`. /// /// Width is selected from the maximum present value; any `None` produces an /// optional variant with a dense values vector. #[must_use] pub fn from_optional(ids: Vec>) -> Self { let len = ids.len(); let mut values = Vec::with_capacity(len); let mut values64: Option> = None; let mut presence: Option> = None; for (idx, id) in ids.into_iter().enumerate() { match id { Some(id) => { if let Some(values64) = &mut values64 { values64.push(id); } else if let Ok(id) = u32::try_from(id) { values.push(id); } else { let mut promoted = Vec::with_capacity(len); promoted.extend(values.iter().copied().map(u64::from)); promoted.push(id); values64 = Some(promoted); } if let Some(presence) = &mut presence { presence.push(true); } } None => { presence .get_or_insert_with(|| { let mut presence = Vec::with_capacity(len); presence.resize(idx, true); presence }) .push(false); } } } if values.is_empty() && values64.is_none() { Self::None } else if let Some(presence) = presence { if let Some(values64) = values64 { Self::OptU64(StagedOptScalar::from_parts( String::new(), presence, values64, )) } else { Self::OptU32(StagedOptScalar::from_parts(String::new(), presence, values)) } } else if let Some(values64) = values64 { Self::u64(values64) } else { Self::u32(values) } } /// Construct from sparse IDs using a precomputed presence classification. #[must_use] pub(crate) fn from_optional_with_presence( ids: impl IntoIterator>, analysis: Option<&PropertyStats>, ) -> Self { let Some(analysis) = analysis else { return Self::None; }; match analysis.presence { Presence::AllNull => Self::None, Presence::AllPresent => { Self::from_dense(ids.into_iter().flatten(), analysis.stats.values_fit_u32()) } Presence::Mixed | Presence::SameAsProp(_) => { Self::from_optional_sparse(ids, analysis.stats.values_fit_u32()) } } } fn from_dense(ids: impl IntoIterator, values_fit_u32: bool) -> Self { if values_fit_u32 { Self::u32( ids.into_iter() .map(|id| { u32::try_from(id).expect("ID analysis guarantees u32-compatible values") }) .collect(), ) } else { Self::u64(ids.into_iter().collect()) } } fn from_optional_sparse( ids: impl IntoIterator>, values_fit_u32: bool, ) -> Self { let ids = ids.into_iter(); let (lower, upper) = ids.size_hint(); let capacity = upper.unwrap_or(lower); let mut presence = Vec::with_capacity(capacity); if values_fit_u32 { let mut values = Vec::with_capacity(capacity); for id in ids { presence.push(id.is_some()); if let Some(id) = id { values.push( u32::try_from(id).expect("ID analysis guarantees u32-compatible values"), ); } } Self::OptU32(StagedOptScalar::from_parts(String::new(), presence, values)) } else { let mut values = Vec::with_capacity(capacity); for id in ids { presence.push(id.is_some()); if let Some(id) = id { values.push(id); } } Self::OptU64(StagedOptScalar::from_parts(String::new(), presence, values)) } } #[must_use] pub fn u32(values: Vec) -> Self { Self::U32(StagedScalar { name: String::new(), values, }) } #[must_use] pub fn opt_u32(values: impl IntoIterator>) -> Self { Self::OptU32(StagedOptScalar::from_optional(String::new(), values)) } #[must_use] pub fn u64(values: Vec) -> Self { Self::U64(StagedScalar { name: String::new(), values, }) } #[must_use] pub fn opt_u64(values: impl IntoIterator>) -> Self { Self::OptU64(StagedOptScalar::from_optional(String::new(), values)) } /// Encode and write the ID column to `enc`. #[hotpath::measure] pub fn write_to(self, enc: &mut Encoder, codecs: &mut Codecs) -> MltResult<()> { match &self { Self::None => Ok(()), Self::U32(v) => codecs.write_u32_scalar_col(ColumnType::Id, None, v, enc), Self::OptU32(v) => codecs.write_opt_u32_scalar_col(ColumnType::OptId, None, v, enc), Self::U64(v) => codecs.write_u64_scalar_col(ColumnType::LongId, None, v, enc), Self::OptU64(v) => codecs.write_opt_u64_scalar_col(ColumnType::OptLongId, None, v, enc), } } } #[cfg(test)] mod tests { use geo_types::Point; use proptest::prelude::*; use rstest::rstest; use crate::decoder::{ColumnType as CT, GeometryValues, RawId, RawIdValue}; use crate::encoder::stream::LogicalEncoder; use crate::encoder::{ Codecs, Encoder, EncoderConfig, ExplicitEncoder, IntEncoder, StagedId, StagedLayer, }; use crate::test_helpers::{dec, into_layer01, parser}; use crate::{Layer, LazyParsed, MltError, MltResult}; /// Round-trip `StagedId` via full layer bytes using an explicit encoder. fn id_roundtrip_via_layer(decoded: &StagedId, int_enc: IntEncoder) -> StagedId { if matches!(decoded, StagedId::None) { return StagedId::from_optional(vec![]); } let n = feature_count(decoded); let mut geometry = GeometryValues::default(); for _ in 0..n { geometry.push_geom(&geo_types::Geometry::::Point(Point::new(0, 0))); } let staged = StagedLayer { name: "id_roundtrip".to_string(), extent: 4096, id: decoded.clone(), geometry, properties: vec![], }; let enc = Encoder::with_explicit(Encoder::default().cfg, ExplicitEncoder::for_id(int_enc)); let mut codecs = Codecs::default(); let enc = staged.encode_into(enc, &mut codecs).expect("encode failed"); let buf = enc.into_layer_bytes().expect("into_layer_bytes failed"); let mut p = parser(); let (_, layer) = Layer::from_bytes(&buf, &mut p).expect("parse failed"); let parsed = into_layer01(layer) .id .expect("expected id column") .into_parsed(&mut dec()) .expect("decode failed"); StagedId::from_optional(parsed.materialize()) } fn id_roundtrip_auto(decoded: &StagedId) -> StagedId { if matches!(decoded, StagedId::None) { return StagedId::from_optional(vec![]); } let n = feature_count(decoded); let mut geometry = GeometryValues::default(); for _ in 0..n { geometry.push_geom(&geo_types::Geometry::::Point(Point::new(0, 0))); } let staged = StagedLayer { name: "id_roundtrip".to_string(), extent: 4096, id: decoded.clone(), geometry, properties: vec![], }; let mut codecs = Codecs::default(); let buf = staged .encode_into(Encoder::default(), &mut codecs) .expect("encode failed") .into_layer_bytes() .expect("into_layer_bytes failed"); let mut p = parser(); let mut d = dec(); let (_, layer) = Layer::from_bytes(&buf, &mut p).expect("parse failed"); assert!(p.reserved() > 0, "parser should reserve bytes after parse"); let layer01 = into_layer01(layer); let result = layer01 .id .expect("expected id column") .into_parsed(&mut d) .expect("decode failed"); assert!( d.consumed() > 0, "decoder should consume bytes after decode" ); StagedId::from_optional(result.materialize()) } fn create_u32_range_ids() -> StagedId { StagedId::from_optional((1u64..=100).map(Some).collect()) } fn create_u64_range_ids() -> StagedId { let base = u64::from(u32::MAX) + 1; StagedId::from_optional((base..base + 50).map(Some).collect()) } fn create_ids_with_nulls() -> StagedId { StagedId::from_optional(vec![Some(10), None, Some(20), None, Some(30)]) } fn create_constant_ids() -> StagedId { StagedId::from_optional(vec![Some(42), Some(42), Some(42), Some(42), Some(42)]) } fn feature_count(ids: &StagedId) -> usize { match ids { StagedId::None => 0, StagedId::U32(v) => v.values.len(), StagedId::OptU32(v) => v.presence.len(), StagedId::U64(v) => v.values.len(), StagedId::OptU64(v) => v.presence.len(), } } fn id_values(ids: &StagedId) -> Vec> { match ids { StagedId::None => Vec::new(), StagedId::U32(v) => v.values.iter().copied().map(u64::from).map(Some).collect(), StagedId::OptU32(v) => { let mut dense = v.values.iter().copied().map(u64::from); v.presence .iter() .map(|&p| if p { dense.next() } else { None }) .collect() } StagedId::U64(v) => v.values.iter().copied().map(Some).collect(), StagedId::OptU64(v) => { let mut dense = v.values.iter().copied(); v.presence .iter() .map(|&p| if p { dense.next() } else { None }) .collect() } } } /// Verify that automatic encoding produces no column for empty or all-null ID lists. #[rstest] #[case::empty(StagedId::from_optional(vec![]))] #[case::all_nulls(StagedId::from_optional(vec![None, None]))] fn test_automatic_encoding_skipped(#[case] input: StagedId) { let mut enc = Encoder::default(); let mut codecs = Codecs::default(); input.write_to(&mut enc, &mut codecs).unwrap(); assert_eq!( enc.meta.len(), 0, "empty or all-null ID list should write no column" ); } /// Verify that automatic encoding produces a column for non-trivial inputs. #[rstest] #[case::short_sequence(StagedId::from_optional(vec![Some(1), Some(2)]))] #[case::sequential_u32(create_u32_range_ids())] #[case::sequential_u64(create_u64_range_ids())] #[case::constant(create_constant_ids())] #[case::with_nulls(create_ids_with_nulls())] fn test_automatic_encoding_produces_output(#[case] input: StagedId) { let mut enc = Encoder::default(); let mut codecs = Codecs::default(); input.write_to(&mut enc, &mut codecs).unwrap(); assert!( !enc.meta.is_empty(), "non-trivial ID list should write a column" ); } #[test] fn test_automatic_optimization_roundtrip_empty() { let decoded = StagedId::from_optional(vec![]); let mut enc = Encoder::default(); let mut codecs = Codecs::default(); decoded.write_to(&mut enc, &mut codecs).unwrap(); assert_eq!(enc.meta.len(), 0, "empty ID list should write no column"); } #[rstest] #[case::sequential_u32(create_u32_range_ids())] #[case::sequential_u64(create_u64_range_ids())] #[case::constant(create_constant_ids())] #[case::with_nulls(create_ids_with_nulls())] fn test_automatic_optimization_roundtrip(#[case] decoded: StagedId) { let decoded_back = id_roundtrip_auto(&decoded); assert_eq!(decoded_back, decoded); } #[test] fn test_manual_optimization_applies_encoder() { let decoded = StagedId::u64((1..=100).collect()); let decoded_back = id_roundtrip_via_layer(&decoded, IntEncoder::varint_with(LogicalEncoder::None)); assert_eq!(id_values(&decoded_back), id_values(&decoded)); } #[test] fn test_manual_u32_roundtrip() { let ids = StagedId::u32(vec![41]); let decoded_back = id_roundtrip_via_layer(&ids, IntEncoder::varint_with(LogicalEncoder::None)); assert_eq!(id_values(&decoded_back), vec![Some(41)]); } #[test] fn test_manual_fastpfor_roundtrip() { let ids = StagedId::u32((0u32..200).map(|i| i * 7 + 3).collect()); let decoded_back = id_roundtrip_via_layer(&ids, IntEncoder::fastpfor()); assert_eq!(decoded_back, ids); } /// Verify that large sequential u32 IDs produce a smaller encoding than plain varint /// (confirming that delta+FastPFOR is being selected automatically). #[test] fn test_auto_fastpfor_beats_varint_for_large_u32_ids() { let ids = StagedId::from_optional((0u64..1000).map(|i| Some(i * 13 + 5)).collect()); let mut auto_enc = Encoder::default(); let mut codecs = Codecs::default(); ids.clone().write_to(&mut auto_enc, &mut codecs).unwrap(); let mut plain_enc = Encoder::with_explicit( Encoder::default().cfg, ExplicitEncoder::for_id(IntEncoder::varint()), ); ids.write_to(&mut plain_enc, &mut codecs).unwrap(); assert!( auto_enc.total_len() <= plain_enc.total_len(), "auto ({} bytes) should not be worse than plain varint ({} bytes)", auto_enc.total_len(), plain_enc.total_len() ); } /// Verify that large u64 IDs round-trip correctly under automatic encoding. #[test] fn test_auto_roundtrip_large_u64_ids() { let base = u64::from(u32::MAX) + 1; let ids = StagedId::from_optional((0u64..1000).map(|i| Some(base + i * 13)).collect()); let decoded_back = id_roundtrip_auto(&ids); assert_eq!(decoded_back, ids); } // Test that each config produces the correct variant and optional stream presence #[rstest] #[case::id32(CT::Id, StagedId::u32(vec![1, 2, 3]))] #[case::empty_id32(CT::Id, StagedId::u32(vec![]))] #[case::opt_id32(CT::OptId, StagedId::opt_u32(vec![Some(1), None, Some(3)]))] #[case::id64(CT::LongId, StagedId::u64(vec![1, 2, 3]))] #[case::opt_id64(CT::OptLongId, StagedId::opt_u64(vec![Some(1), None, Some(3)]))] fn test_config_produces_correct_variant(#[case] column_type: CT, #[case] input: StagedId) { let int_enc = IntEncoder::varint_with(LogicalEncoder::None); with_encoded_raw_id(&input, int_enc, |raw_id| { match column_type { CT::OptId | CT::Id => assert!(matches!(raw_id.value, RawIdValue::Id32(_))), CT::LongId | CT::OptLongId => assert!(matches!(raw_id.value, RawIdValue::Id64(_))), _ => unreachable!(), } match column_type { CT::OptId | CT::OptLongId => assert!(raw_id.presence.0.is_some()), CT::Id | CT::LongId => assert!(raw_id.presence.0.is_none()), _ => unreachable!(), } }); } #[rstest] #[case::id32_basic(StagedId::u32(vec![1, 2, 100, 1000]))] #[case::id32_single(StagedId::u32(vec![42]))] #[case::id32_boundaries(StagedId::u32(vec![0, u32::MAX]))] #[case::id64_basic(StagedId::u64(vec![1, 2, 100, 1000]))] #[case::id64_single(StagedId::u64(vec![u64::MAX]))] #[case::id64_boundaries(StagedId::u64(vec![0, u64::MAX]))] #[case::id64_large_values(StagedId::u64(vec![0, u64::from(u32::MAX), u64::from(u32::MAX) + 1, u64::MAX]))] #[case::opt_id32_with_nulls(StagedId::opt_u32(vec![Some(1), None, Some(100), None, Some(1000)]))] #[case::opt_id32_no_nulls(StagedId::opt_u32(vec![Some(1), Some(2), Some(3)]))] #[case::opt_id32_single_null(StagedId::opt_u32(vec![None]))] #[case::opt_id64_with_nulls(StagedId::opt_u64(vec![Some(1), None, Some(u64::from(u32::MAX) + 1), None, Some(u64::MAX)]))] #[case::opt_id64_all_nulls(StagedId::opt_u64(vec![None, None, None]))] #[case::none(StagedId::None)] #[case::empty_id32(StagedId::u32(vec![]))] fn test_roundtrip(#[case] ids: StagedId) { let int_enc = IntEncoder::varint_with(LogicalEncoder::None); assert_roundtrip(&ids, int_enc); } #[rstest] fn test_sequential_ids( #[values(LogicalEncoder::None)] logical: LogicalEncoder, #[values(CT::Id, CT::OptId, CT::LongId, CT::OptLongId)] column_type: CT, ) { let input = match column_type { CT::Id => StagedId::u32((1..=100).collect()), CT::OptId => StagedId::opt_u32((1..=100).map(Some)), CT::LongId => StagedId::u64((1..=100).collect()), CT::OptLongId => StagedId::opt_u64((1..=100).map(Some)), _ => unreachable!(), }; let int_enc = IntEncoder::varint_with(logical); assert_roundtrip(&input, int_enc); } proptest! { #[test] fn test_roundtrip_opt_id32( ids in prop::collection::vec(prop::option::of(any::()), 1..100), logical in any::() ) { prop_assert_roundtrip(&StagedId::opt_u32(ids), IntEncoder::varint_with(logical))?; } #[test] fn test_roundtrip_id64( ids in prop::collection::vec(any::(), 1..100), logical in any::() ) { prop_assert_roundtrip(&StagedId::u64(ids), IntEncoder::varint_with(logical))?; } #[test] fn test_roundtrip_id32( ids in prop::collection::vec(any::(), 1..100), logical in any::() ) { prop_assert_roundtrip(&StagedId::u32(ids), IntEncoder::varint_with(logical))?; } #[test] fn test_roundtrip_opt_id64( ids in prop::collection::vec(prop::option::of(any::()), 1..100), logical in any::() ) { prop_assert_roundtrip(&StagedId::opt_u64(ids), IntEncoder::varint_with(logical))?; } #[test] fn test_correct_variant_produced_id32( ids in prop::collection::vec(1u32..1000u32, 1..50), logical in any::() ) { assert_produces_correct_variant(&StagedId::u32(ids), CT::Id, IntEncoder::varint_with(logical))?; } #[test] fn test_correct_variant_produced_id64( ids in prop::collection::vec(any::(), 1..50), logical in any::() ) { assert_produces_correct_variant(&StagedId::u64(ids), CT::LongId, IntEncoder::varint_with(logical))?; } } /// Round-trip `StagedId` via full layer bytes (encode → bytes → parse → decode). fn assert_roundtrip(ids: &StagedId, int_enc: IntEncoder) { prop_assert_roundtrip(ids, int_enc).expect("roundtrip failed"); } fn prop_assert_roundtrip(ids: &StagedId, int_enc: IntEncoder) -> Result<(), TestCaseError> { let res = roundtrip_id_values(ids, int_enc) .map_err(|e| TestCaseError::Fail(format!("Roundtrip failed: {e:?}").into()))?; let expected = StagedId::from_optional(id_values(ids)); prop_assert_eq!(id_values(&res), id_values(&expected)); Ok(()) } fn roundtrip_id_values(decoded: &StagedId, int_enc: IntEncoder) -> MltResult { if matches!(decoded, StagedId::None) { return Ok(StagedId::from_optional(vec![])); } let n = feature_count(decoded); let mut geometry = GeometryValues::default(); for _ in 0..n { geometry.push_geom(&geo_types::Geometry::::Point(Point::new(0, 0))); } let staged = StagedLayer { name: "id_roundtrip".to_string(), extent: 4096, id: decoded.clone(), geometry, properties: vec![], }; let enc = Encoder::with_explicit(EncoderConfig::default(), ExplicitEncoder::for_id(int_enc)); let mut codecs = Codecs::default(); let enc = staged.encode_into(enc, &mut codecs)?; let buf = enc.into_layer_bytes()?; let (_, layer) = Layer::from_bytes(&buf, &mut parser())?; let Layer::Tag01(layer01) = layer else { return Err(MltError::NotDecoded("expected Tag01 layer")); }; // When all source IDs were null, the encoder skips the ID column entirely. // On decode, the absent column is semantically identical to all-null IDs. match layer01.id { Some(id) => { let parsed = id.into_parsed(&mut dec())?; Ok(StagedId::from_optional(parsed.materialize())) } None => Ok(StagedId::from_optional(vec![None; n])), } } /// Encode `ids` into a full layer, parse the raw ID, and pass it to `f`. fn with_encoded_raw_id( ids: &StagedId, int_enc: IntEncoder, f: impl FnOnce(&RawId<'_>) -> R, ) -> R { // Encode a full StagedLayer, parse the layer back out, and inspect the raw ID field. // This exercises the ID encoding as it appears in a real layer payload. let n = feature_count(ids); let mut geometry = GeometryValues::default(); for _ in 0..n { geometry.push_geom(&geo_types::Geometry::::Point(Point::new(0, 0))); } let staged = StagedLayer { name: "id_test".to_string(), extent: 4096, id: ids.clone(), geometry, properties: vec![], }; let enc = Encoder::with_explicit(EncoderConfig::default(), ExplicitEncoder::for_id(int_enc)); let mut codecs = Codecs::default(); let enc = staged.encode_into(enc, &mut codecs).expect("encode failed"); let buf = enc.into_layer_bytes().expect("into_layer_bytes failed"); let (_, layer) = Layer::from_bytes(&buf, &mut parser()).expect("parse failed"); let Layer::Tag01(layer01) = layer else { panic!("expected Tag01") }; let Some(LazyParsed::Raw(raw_id)) = layer01.id else { panic!("expected raw id") }; f(&raw_id) } fn assert_produces_correct_variant( input: &StagedId, column_type: CT, int_enc: IntEncoder, ) -> Result<(), TestCaseError> { with_encoded_raw_id(input, int_enc, |raw_id| { if matches!(column_type, CT::Id | CT::OptId) { prop_assert!( matches!(raw_id.value, RawIdValue::Id32(_)), "Expected Id32 variant" ); } else { prop_assert!( matches!(raw_id.value, RawIdValue::Id64(_)), "Expected Id64 variant" ); } if matches!(column_type, CT::OptId | CT::OptLongId) { prop_assert!( raw_id.presence.0.is_some(), "Expected optional stream to be present" ); } else { prop_assert!(raw_id.presence.0.is_none(), "Expected no optional stream"); } Ok(()) }) } } ================================================ FILE: rust/mlt-core/src/encoder/mod.rs ================================================ mod analyze; #[cfg(all(not(test), feature = "arbitrary"))] mod fuzzing; mod geometry; mod id; pub(crate) mod model; mod optimizer; mod property; mod sort; mod stream; #[cfg(any(test, feature = "__private"))] mod tests; mod tile; mod unknown; mod writer; #[cfg(not(feature = "__private"))] pub(crate) use geometry::VertexBufferType; #[cfg(feature = "__private")] pub use geometry::VertexBufferType; pub use id::StagedId; #[cfg(feature = "__private")] pub use model::{ColumnKind, CurveParams, ExplicitEncoder, StagedLayer, StrEncoding, StreamCtx}; pub use model::{EncodedUnknown, EncoderConfig}; #[cfg(all(test, not(feature = "__private")))] pub(crate) use model::{ExplicitEncoder, StagedLayer, StrEncoding}; #[cfg(any(test, feature = "__private"))] pub use optimizer::Presence; pub(crate) use property::*; #[cfg(feature = "__private")] pub use property::{StagedProperty, StagedSharedDict}; pub use sort::SortStrategy; pub(crate) use sort::spatial_sort_likely_to_help; pub(crate) use stream::*; #[cfg(feature = "__private")] pub use stream::{Codecs, IntEncoder, LogicalEncoder, PhysicalEncoder}; #[cfg(any(test, feature = "__private"))] pub use tests::stage_tile; pub use writer::Encoder; ================================================ FILE: rust/mlt-core/src/encoder/model.rs ================================================ use derive_debug::Dbg; use crate::decoder::{DictionaryType, GeometryValues, StreamType}; use crate::encoder::geometry::VertexBufferType; use crate::encoder::{IntEncoder, StagedId, StagedProperty}; /// Owned variant of `Unknown`. #[derive(Debug, Clone, Default, PartialEq)] pub struct EncodedUnknown { pub(crate) tag: u8, pub(crate) value: Vec, } /// Parameters derived from the vertex set of a feature collection, used to /// normalize coordinates before space-filling-curve key computation. #[derive(Debug, Clone, Copy, PartialEq)] pub struct CurveParams { pub shift: u32, pub bits: u32, } impl Default for CurveParams { fn default() -> Self { Self { shift: 0, bits: 1 } } } impl CurveParams { /// Compute params from a flat `[x0, y0, x1, y1, …]` vertex slice. #[must_use] pub fn from_vertices(vertices: &[i32]) -> Self { if vertices.is_empty() { return Self::default(); } let (min, max) = vertices .iter() .fold((i32::MAX, i32::MIN), |(mn, mx), &v| (mn.min(v), mx.max(v))); crate::codecs::hilbert::hilbert_curve_params_from_bounds(min, max) } } /// Columnar layer data being prepared for encoding (stage 2 of the encoding pipeline). /// /// Holds fully-owned columnar data. Constructed directly (synthetics, benches) or /// converted from [`TileLayer`](crate::TileLayer). /// Consumed by encoding via [`StagedLayer::encode_into`] or `StagedLayer::encode_explicit` /// (with explicit encoding mode enabled). #[derive(Debug, PartialEq, Clone)] pub struct StagedLayer { pub name: String, pub extent: u32, pub id: StagedId, pub geometry: GeometryValues, pub properties: Vec, } /// Global encoder settings controlling which optimization strategies are attempted. #[derive(Debug, Clone, Copy, PartialEq, Hash)] #[expect( clippy::struct_excessive_bools, reason = "enums would not model this better, not a state machine" )] pub struct EncoderConfig { /// Generate tessellation data for polygons and multi-polygons. pub tessellate: bool, /// Try sorting features by the Z-order (Morton) curve index of their first vertex. pub try_spatial_morton_sort: bool, /// Try sorting features by the Hilbert curve index of their first vertex. pub try_spatial_hilbert_sort: bool, /// Try sorting features by their feature ID in ascending order. pub try_id_sort: bool, /// Allow `FSST` string compression pub allow_fsst: bool, /// Allow `FastPFOR` integer compression pub allow_fpf: bool, /// Allow string grouping into shared dictionaries pub allow_shared_dict: bool, } impl Default for EncoderConfig { fn default() -> Self { Self { tessellate: false, try_spatial_morton_sort: true, try_spatial_hilbert_sort: true, try_id_sort: true, allow_fsst: true, allow_fpf: true, allow_shared_dict: true, } } } /// How to encode a string column. /// /// Used by [`ExplicitEncoder`] to control per-column string encoding in the /// explicit (synthetics / `__private`) path and in property-encoding helpers. /// /// Publicly visible only when the `__private` feature is enabled (re-exported from /// [`crate::encoder`]). Always compiled so that the unified property-encoding path /// can reference it without feature flags. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum StrEncoding { Plain, Dict, Fsst, FsstDict, } #[derive(Debug, Clone, Copy, PartialEq)] pub enum ColumnKind { Id, Geometry, Property, } /// Context for per-stream encoding decisions in [`ExplicitEncoder`] callbacks. #[derive(Clone, Copy, Debug, PartialEq)] pub struct StreamCtx<'a> { pub kind: ColumnKind, pub stream_type: StreamType, pub name: &'a str, pub subname: &'a str, } impl<'a> StreamCtx<'a> { /// Stream with a logical sub-part (e.g. string column `"lengths"` / `"offsets"`, shared-dict child suffix). #[inline] #[must_use] pub const fn new( kind: ColumnKind, stream_type: StreamType, name: &'a str, subname: &'a str, ) -> Self { Self { kind, stream_type, name, subname, } } #[inline] #[must_use] pub const fn id(stream_type: StreamType) -> Self { Self::new(ColumnKind::Id, stream_type, "", "") } #[inline] #[must_use] pub const fn geom(stream_type: StreamType, name: &'a str) -> Self { Self::new(ColumnKind::Geometry, stream_type, name, "") } #[inline] #[must_use] pub const fn prop(stream_type: StreamType, name: &'a str) -> Self { Self::new(ColumnKind::Property, stream_type, name, "") } #[inline] #[must_use] pub const fn prop_data(name: &'a str) -> Self { let stream_type = StreamType::Data(DictionaryType::None); Self::new(ColumnKind::Property, stream_type, name, "") } #[inline] #[must_use] pub const fn prop2(stream_type: StreamType, prefix: &'a str, suffix: &'a str) -> Self { Self::new(ColumnKind::Property, stream_type, prefix, suffix) } } /// Explicit, deterministic encoding configuration for synthetics and tests. /// /// All encoding choices are caller-specified via callbacks so one struct can cover /// any combination without per-stream boilerplate. /// /// Always compiled; publicly visible only when the `__private` feature is enabled /// (re-exported from [`crate::encoder`]). #[derive(Dbg)] pub struct ExplicitEncoder { /// Vertex buffer layout for geometry streams. pub vertex_buffer_type: VertexBufferType, /// Per-stream override for the skip-empty-stream rule used by `write_geo_u32_stream`. #[dbg(skip)] pub force_stream: Box Fn(&'a StreamCtx<'a>) -> bool>, /// Return the [`IntEncoder`] for a stream identified by [`StreamCtx`]. #[dbg(skip)] pub get_int_encoder: Box Fn(&'a StreamCtx<'a>) -> IntEncoder>, /// Return the string encoding strategy for a string property column. #[dbg(skip)] pub get_str_encoding: Box StrEncoding>, } ================================================ FILE: rust/mlt-core/src/encoder/optimizer.rs ================================================ use bitvec::vec::BitVec; use crate::decoder::{Morton, PropKind, TileLayer}; use crate::encoder::model::{CurveParams, StagedLayer}; use crate::encoder::property::encode::write_properties; use crate::encoder::{ Codecs, Encoder, EncoderConfig, SortStrategy, StagedId, spatial_sort_likely_to_help, }; use crate::{MltError, MltResult, PropValue}; impl StagedLayer { /// Encode and serialize the layer directly into `enc`, without creating any /// intermediate representation. /// /// This is the hot path inside `TileLayer::encode`: each sort-strategy /// trial calls this method on its own fresh `Encoder`, and only the /// `Encoder` with the smallest `total_len()` is kept. #[hotpath::measure] pub fn encode_into(self, mut enc: Encoder, codecs: &mut Codecs) -> MltResult { let column_count = usize::from(!matches!(&self.id, StagedId::None)) + 1 // geometry + self.properties.len(); let Self { name, extent, id, geometry, properties, } = self; id.write_to(&mut enc, codecs)?; geometry.write_to(&mut enc, codecs)?; write_properties(&properties, &mut enc, codecs)?; enc.write_header(&name, extent, column_count)?; Ok(enc) } } /// Seed the encoder's curve-derived caches so the Hilbert/Morton dictionary /// builders skip their min/max scan. `Morton::new` returns `Err` when bits > 16; /// `dict_may_be_beneficial` reads `morton_cache.is_none()` and falls back to /// a Vec2-only path in that case. fn seed_curve_caches(enc: &mut Encoder, curve_params: CurveParams) { enc.hilbert_cache = Some(curve_params); enc.morton_cache = Morton::new(curve_params.bits, curve_params.shift).ok(); } /// Feature-count threshold above which the spatial trial is subject to the /// bounding-box pruning heuristic. const SORT_TRIAL_THRESHOLD: usize = 512; impl TileLayer { /// Encode a [`TileLayer`] to bytes, automatically optimizing all encoding choices. /// /// This is the primary encoding entry point. It: /// 1. Determines which sort strategies to try based on `cfg` /// 2. Tries each sort strategy, encoding and measuring the output size /// 3. Returns the smallest encoding as a complete layer record (including tag and length prefix) /// /// All encoding choices — sort order, per-stream integer encodings, string compression, /// vertex buffer layout — are selected automatically to minimize output size. #[hotpath::measure] pub fn encode(self, cfg: EncoderConfig) -> MltResult> { if self.features.is_empty() { return Ok(Vec::new()); } let mut sort_by = vec![SortStrategy::Unsorted]; let try_spatial_sort = cfg.try_spatial_morton_sort || cfg.try_spatial_hilbert_sort; if try_spatial_sort && (self.features.len() < SORT_TRIAL_THRESHOLD || spatial_sort_likely_to_help(&self)) { if cfg.try_spatial_morton_sort { sort_by.push(SortStrategy::SpatialMorton); } if cfg.try_spatial_hilbert_sort { sort_by.push(SortStrategy::SpatialHilbert); } } if cfg.try_id_sort { sort_by.push(SortStrategy::Id); } let stats = self.analyze(cfg.allow_shared_dict)?; // Bounds are order-invariant, so this scan is shared across every // sort trial and the encoder's Hilbert/Morton dictionary builders. let curve_params = self.curve_params(); // `Encoder::preserve_results` clears caches only on the moved-out // archive, so a single seeding here serves every trial that reuses // `enc`. let mut enc = Encoder::new(cfg); seed_curve_caches(&mut enc, curve_params); let (last, init) = sort_by.split_last().expect("at least one strategy"); if init.is_empty() { let mut codecs = Codecs::default(); StagedLayer::from_tile(self, *last, &stats, cfg.tessellate, curve_params) .encode_into(enc, &mut codecs)? } else { let mut codecs = Codecs::default(); enc = { let first = init[0]; StagedLayer::from_tile(self.clone(), first, &stats, cfg.tessellate, curve_params) .encode_into(enc, &mut codecs)? }; let mut best = enc.preserve_results(); // Clone for all-but-last strategies for &sort in &init[1..] { let layer = StagedLayer::from_tile( self.clone(), sort, &stats, cfg.tessellate, curve_params, ); enc = layer.encode_into(enc, &mut codecs)?; if enc.total_len() < best.total_len() { best = enc.preserve_results(); } } // Last strategy: consume self, no clone let layer = StagedLayer::from_tile(self, *last, &stats, cfg.tessellate, curve_params); enc = layer.encode_into(enc, &mut codecs)?; if enc.total_len() < best.total_len() { best = enc.preserve_results(); } best } .into_layer_bytes() } } /// Row-order-independent presence classification for IDs and properties. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Presence { /// No feature has a value for this logical column. AllNull, /// Every feature has a value for this logical column. AllPresent, /// Some, but not all, features have a value for this logical column. Mixed, /// Mixed presence with the same per-feature mask as an earlier property column. SameAsProp(usize), } impl Presence { /// Create presence value #[must_use] pub fn from_bits(bits: &BitVec, existing: &[(BitVec, usize)]) -> Self { if bits.not_any() { Self::AllNull } else if bits.all() { Self::AllPresent } else if let Some((_, idx)) = existing.iter().find(|(v, _)| v == bits) { Self::SameAsProp(*idx) } else { Self::Mixed } } } /// How a property participates in a shared dictionary group. #[derive(Debug, Clone, PartialEq, Eq)] pub enum SharedDictRole { /// The property is encoded as a standalone column. None, /// The property is the first column in this group and emits the shared dictionary with this prefix. Owner(String), /// The property is emitted by the group owner at this property index. Member(usize), } /// Row-order-independent facts for a single property column. #[derive(Debug, Clone, PartialEq, Eq)] pub struct PropertyStats { pub presence: Presence, pub stats: PropertyTypedStats, } /// Row-order-independent layer facts computed once before sort trials. #[derive(Debug, Clone, PartialEq, Eq)] pub struct LayerStats { pub id: Option, pub properties: Vec, } /// Row-order-independent value statistics for a property column. #[derive(Debug, Clone, Default, PartialEq, Eq)] pub enum PropertyTypedStats { /// No present values. #[default] None, Bool, Signed { min: i64, max: i64, }, Unsigned { min: u64, max: u64, }, F32, F64, String { shared_dict: SharedDictRole, }, } impl PropertyTypedStats { #[must_use] pub fn values_fit_u32(&self) -> bool { match self { Self::None | Self::Bool | Self::F32 | Self::F64 | Self::String { .. } => false, Self::Signed { min, max } => *min >= 0 && u32::try_from(*max).is_ok(), Self::Unsigned { max, .. } => u32::try_from(*max).is_ok(), } } #[must_use] pub fn shared_dict(&self) -> SharedDictRole { match self { Self::String { shared_dict, .. } => shared_dict.clone(), _ => SharedDictRole::None, } } pub(crate) fn set_shared_dict(&mut self, role: SharedDictRole) { match self { Self::String { shared_dict, .. } => *shared_dict = role, _ => debug_assert_eq!(role, SharedDictRole::None), } } pub(crate) fn push( &mut self, prop: &PropValue, column_idx: usize, property_name: &str, ) -> MltResult { match prop { PropValue::Bool(Some(_)) => { self.merge_same_kind(Self::Bool, column_idx, property_name)?; } PropValue::I8(Some(v)) => { self.merge_signed(i64::from(*v), column_idx, property_name)?; } PropValue::U8(Some(v)) => { self.merge_unsigned(u64::from(*v), column_idx, property_name)?; } PropValue::I32(Some(v)) => { self.merge_signed(i64::from(*v), column_idx, property_name)?; } PropValue::U32(Some(v)) => { self.merge_unsigned(u64::from(*v), column_idx, property_name)?; } PropValue::I64(Some(v)) => self.merge_signed(*v, column_idx, property_name)?, PropValue::U64(Some(v)) => self.merge_unsigned(*v, column_idx, property_name)?, PropValue::F32(Some(_)) => { self.merge_same_kind(Self::F32, column_idx, property_name)?; } PropValue::F64(Some(_)) => { self.merge_same_kind(Self::F64, column_idx, property_name)?; } PropValue::Str(Some(_)) => self.merge_string(column_idx, property_name)?, _ => return Ok(false), } Ok(true) } fn merge_signed( &mut self, value: i64, column_idx: usize, property_name: &str, ) -> MltResult<()> { match self { Self::None => { *self = Self::Signed { min: value, max: value, }; } Self::Signed { min, max } => { *min = (*min).min(value); *max = (*max).max(value); } _ => return mixed_prop_err(column_idx, property_name), } Ok(()) } fn merge_unsigned( &mut self, value: u64, column_idx: usize, property_name: &str, ) -> MltResult<()> { match self { Self::None => { *self = Self::Unsigned { min: value, max: value, }; } Self::Unsigned { min, max } => { *min = (*min).min(value); *max = (*max).max(value); } _ => return mixed_prop_err(column_idx, property_name), } Ok(()) } fn merge_string(&mut self, column_idx: usize, property_name: &str) -> MltResult<()> { match self { Self::None => { *self = Self::String { shared_dict: SharedDictRole::None, }; } Self::String { .. } => {} _ => return mixed_prop_err(column_idx, property_name), } Ok(()) } fn merge_same_kind( &mut self, kind: Self, column_idx: usize, property_name: &str, ) -> MltResult<()> { match self { Self::None => *self = kind, Self::Bool if matches!(kind, Self::Bool) => {} Self::F32 if matches!(kind, Self::F32) => {} Self::F64 if matches!(kind, Self::F64) => {} _ => return mixed_prop_err(column_idx, property_name), } Ok(()) } } impl TileLayer { /// Analyze a [`TileLayer`] and return reusable ID/property facts for the optimizer. #[hotpath::measure] pub(crate) fn analyze(&self, allow_shared_dict: bool) -> MltResult { let mut property_bits = Vec::with_capacity(self.property_names.len()); let mut properties = self.analyze_properties(&mut property_bits)?; let id = self.analyze_ids(&property_bits); if allow_shared_dict { self.group_string_properties(&mut properties); } Ok(LayerStats { id, properties }) } fn analyze_ids(&self, property_bits: &[(BitVec, usize)]) -> Option { let mut min = u64::MAX; let mut max = 0u64; let mut bits = BitVec::::with_capacity(self.features.len()); for feature in &self.features { if let Some(id) = feature.id { min = min.min(id); max = max.max(id); bits.push(true); } else { bits.push(false); } } let presence = Presence::from_bits(&bits, property_bits); if presence == Presence::AllNull { None } else { Some(PropertyStats { presence, stats: PropertyTypedStats::Unsigned { min, max }, }) } } fn analyze_properties( &self, property_bits: &mut Vec<(BitVec, usize)>, ) -> MltResult> { self.property_names .iter() .enumerate() .map(|(col_idx, name)| -> MltResult { let mut kind = None; let mut stats = PropertyTypedStats::default(); let mut bits = BitVec::::with_capacity(self.features.len()); for feature in &self.features { let prop = feature.properties.get(col_idx); if let Some(prop_kind) = prop.map(PropKind::from) { match kind { Some(kind) if kind != prop_kind => { return mixed_prop_err(col_idx, name.as_str()); } None => kind = Some(prop_kind), _ => {} } } if let Some(prop) = prop && stats.push(prop, col_idx, name)? { bits.push(true); } else { bits.push(false); } } let presence = Presence::from_bits(&bits, property_bits); if presence == Presence::Mixed { property_bits.push((bits, col_idx)); } Ok(PropertyStats { presence, stats }) }) .collect() } } #[inline] fn mixed_prop_err(column_idx: usize, property_name: &str) -> MltResult { Err(MltError::MixedPropertyTypes( column_idx, property_name.to_owned(), )) } ================================================ FILE: rust/mlt-core/src/encoder/property/encode.rs ================================================ use super::model::{StagedOptScalar, StagedProperty}; use crate::MltResult; use crate::decoder::{ColumnType, DictionaryType, StreamType}; use crate::encoder::model::StreamCtx; use crate::encoder::{Codecs, Encoder, StagedScalar, StagedStrings}; /// Encode all property columns and write them to `enc`. #[hotpath::measure] pub fn write_properties( props: &[StagedProperty], enc: &mut Encoder, codecs: &mut Codecs, ) -> MltResult<()> { for prop in props { write_prop(prop, enc, codecs)?; } Ok(()) } /// Encode a single property column, dispatching on variant. #[hotpath::measure] fn write_prop(prop: &StagedProperty, enc: &mut Encoder, codecs: &mut Codecs) -> MltResult<()> { use ColumnType as CT; use StagedProperty as D; match prop { D::Bool(v) => { enc.write_column_header(CT::Bool, &v.name)?; let values = v.values.iter().copied(); codecs.write_bool_stream(values, StreamType::Data(DictionaryType::None), enc) } D::OptBool(v) => { codecs.begin_opt_col(CT::OptBool, &v.name, &v.presence, enc)?; let values = v.values.iter().copied(); codecs.write_bool_stream(values, StreamType::Data(DictionaryType::None), enc) } D::F32(v) => { enc.write_column_header(CT::F32, &v.name)?; codecs.write_float_stream(&v.values, StreamType::Data(DictionaryType::None), enc) } D::OptF32(v) => { codecs.begin_opt_col(CT::OptF32, &v.name, &v.presence, enc)?; codecs.write_float_stream(&v.values, StreamType::Data(DictionaryType::None), enc) } D::F64(v) => { enc.write_column_header(CT::F64, &v.name)?; codecs.write_float_stream(&v.values, StreamType::Data(DictionaryType::None), enc) } D::OptF64(v) => { codecs.begin_opt_col(CT::OptF64, &v.name, &v.presence, enc)?; codecs.write_float_stream(&v.values, StreamType::Data(DictionaryType::None), enc) } D::I8(v) => { enc.write_column_header(CT::I8, &v.name)?; codecs.write_int_stream(&v.values, &StreamCtx::prop_data(&v.name), enc) } D::OptI8(v) => { codecs.begin_opt_col(CT::OptI8, &v.name, &v.presence, enc)?; codecs.write_int_stream(&v.values, &StreamCtx::prop_data(&v.name), enc) } D::U8(v) => { enc.write_column_header(CT::U8, &v.name)?; codecs.write_int_stream(&v.values, &StreamCtx::prop_data(&v.name), enc) } D::OptU8(v) => { codecs.begin_opt_col(CT::OptU8, &v.name, &v.presence, enc)?; codecs.write_int_stream(&v.values, &StreamCtx::prop_data(&v.name), enc) } D::I32(v) => { enc.write_column_header(CT::I32, &v.name)?; codecs.write_int_stream(&v.values, &StreamCtx::prop_data(&v.name), enc) } D::OptI32(v) => { codecs.begin_opt_col(CT::OptI32, &v.name, &v.presence, enc)?; codecs.write_int_stream(&v.values, &StreamCtx::prop_data(&v.name), enc) } D::U32(v) => codecs.write_u32_scalar_col(CT::U32, Some(&v.name), v, enc), D::OptU32(v) => codecs.write_opt_u32_scalar_col(CT::OptU32, Some(&v.name), v, enc), D::I64(v) => { enc.write_column_header(CT::I64, &v.name)?; codecs.write_int_stream(&v.values, &StreamCtx::prop_data(&v.name), enc) } D::OptI64(v) => { codecs.begin_opt_col(CT::OptI64, &v.name, &v.presence, enc)?; codecs.write_int_stream(&v.values, &StreamCtx::prop_data(&v.name), enc) } D::U64(v) => codecs.write_u64_scalar_col(CT::U64, Some(&v.name), v, enc), D::OptU64(v) => codecs.write_opt_u64_scalar_col(CT::OptU64, Some(&v.name), v, enc), D::Str(v) => { enc.write_column_header(ColumnType::Str, &v.name)?; codecs.write_str_col(v, None, enc) } D::OptStr(v) => { enc.write_column_header(ColumnType::OptStr, &v.name)?; codecs.write_str_col(v, Some(v), enc) } D::SharedDict(v) => codecs.write_shared_dict(v, enc), } } impl Codecs { /// Writes the column-type byte, name, and presence stream for an optional column. /// /// Presence is always written: the invariant is that an optional-variant column always /// has a presence stream. Ensuring the column is non-empty and not all-null is the /// caller's responsibility. fn begin_opt_col( &mut self, ct: ColumnType, name: &str, presence: &[bool], enc: &mut Encoder, ) -> MltResult<()> { enc.write_column_header(ct, name)?; self.write_presence_stream(presence.iter().copied(), enc) } pub(crate) fn write_u32_scalar_col( &mut self, ct: ColumnType, name: Option<&str>, v: &StagedScalar, enc: &mut Encoder, ) -> MltResult<()> { begin_scalar_col(ct, name, enc)?; self.write_int_stream(&v.values, &scalar_ctx(name), enc) } pub(crate) fn write_opt_u32_scalar_col( &mut self, ct: ColumnType, name: Option<&str>, v: &StagedOptScalar, enc: &mut Encoder, ) -> MltResult<()> { begin_scalar_col(ct, name, enc)?; self.write_presence_stream(v.presence.iter().copied(), enc)?; self.write_int_stream(&v.values, &scalar_ctx(name), enc) } pub(crate) fn write_u64_scalar_col( &mut self, ct: ColumnType, name: Option<&str>, v: &StagedScalar, enc: &mut Encoder, ) -> MltResult<()> { begin_scalar_col(ct, name, enc)?; self.write_int_stream(&v.values, &scalar_ctx(name), enc) } pub(crate) fn write_opt_u64_scalar_col( &mut self, ct: ColumnType, name: Option<&str>, v: &StagedOptScalar, enc: &mut Encoder, ) -> MltResult<()> { let presence_bools = v.presence.iter().copied(); begin_scalar_col(ct, name, enc)?; self.write_presence_stream(presence_bools, enc)?; self.write_int_stream(&v.values, &scalar_ctx(name), enc) } } fn begin_scalar_col(ct: ColumnType, name: Option<&str>, enc: &mut Encoder) -> MltResult<()> { if let Some(name) = name { enc.write_column_header(ct, name) } else { enc.write_column_type(ct) } } fn scalar_ctx(name: Option<&str>) -> StreamCtx<'_> { match name { Some(name) => StreamCtx::prop_data(name), None => StreamCtx::id(StreamType::Data(DictionaryType::None)), } } impl StagedProperty { #[must_use] pub fn bool(name: impl Into, values: Vec) -> Self { Self::Bool(StagedScalar { name: name.into(), values, }) } #[must_use] pub fn i8(name: impl Into, values: Vec) -> Self { Self::I8(StagedScalar { name: name.into(), values, }) } #[must_use] pub fn u8(name: impl Into, values: Vec) -> Self { Self::U8(StagedScalar { name: name.into(), values, }) } #[must_use] pub fn i32(name: impl Into, values: Vec) -> Self { Self::I32(StagedScalar { name: name.into(), values, }) } #[must_use] pub fn u32(name: impl Into, values: Vec) -> Self { Self::U32(StagedScalar { name: name.into(), values, }) } #[must_use] pub fn i64(name: impl Into, values: Vec) -> Self { Self::I64(StagedScalar { name: name.into(), values, }) } #[must_use] pub fn u64(name: impl Into, values: Vec) -> Self { Self::U64(StagedScalar { name: name.into(), values, }) } #[must_use] pub fn f32(name: impl Into, values: Vec) -> Self { Self::F32(StagedScalar { name: name.into(), values, }) } #[must_use] pub fn f64(name: impl Into, values: Vec) -> Self { Self::F64(StagedScalar { name: name.into(), values, }) } #[must_use] pub fn str(name: impl Into, values: impl IntoIterator>) -> Self { Self::Str(StagedStrings::from_strings(name, values)) } #[must_use] pub fn opt_str( name: impl Into, values: impl IntoIterator>>, ) -> Self { Self::OptStr(StagedStrings::from_optional(name, values)) } // ── Optional constructors ───────────────────────────────────────────────── #[must_use] pub fn opt_bool( name: impl Into, values: impl IntoIterator>, ) -> Self { Self::OptBool(StagedOptScalar::from_optional(name, values)) } #[must_use] pub fn opt_i8(name: impl Into, values: impl IntoIterator>) -> Self { Self::OptI8(StagedOptScalar::from_optional(name, values)) } #[must_use] pub fn opt_u8(name: impl Into, values: impl IntoIterator>) -> Self { Self::OptU8(StagedOptScalar::from_optional(name, values)) } #[must_use] pub fn opt_i32(name: impl Into, values: impl IntoIterator>) -> Self { Self::OptI32(StagedOptScalar::from_optional(name, values)) } #[must_use] pub fn opt_u32(name: impl Into, values: impl IntoIterator>) -> Self { Self::OptU32(StagedOptScalar::from_optional(name, values)) } #[must_use] pub fn opt_i64(name: impl Into, values: impl IntoIterator>) -> Self { Self::OptI64(StagedOptScalar::from_optional(name, values)) } #[must_use] pub fn opt_u64(name: impl Into, values: impl IntoIterator>) -> Self { Self::OptU64(StagedOptScalar::from_optional(name, values)) } #[must_use] pub fn opt_f32(name: impl Into, values: impl IntoIterator>) -> Self { Self::OptF32(StagedOptScalar::from_optional(name, values)) } #[must_use] pub fn opt_f64(name: impl Into, values: impl IntoIterator>) -> Self { Self::OptF64(StagedOptScalar::from_optional(name, values)) } #[must_use] pub fn name(&self) -> &str { match self { Self::Bool(v) => &v.name, Self::I8(v) => &v.name, Self::U8(v) => &v.name, Self::I32(v) => &v.name, Self::U32(v) => &v.name, Self::I64(v) => &v.name, Self::U64(v) => &v.name, Self::F32(v) => &v.name, Self::F64(v) => &v.name, Self::OptBool(v) => &v.name, Self::OptI8(v) => &v.name, Self::OptU8(v) => &v.name, Self::OptI32(v) => &v.name, Self::OptU32(v) => &v.name, Self::OptI64(v) => &v.name, Self::OptU64(v) => &v.name, Self::OptF32(v) => &v.name, Self::OptF64(v) => &v.name, Self::Str(v) | Self::OptStr(v) => &v.name, Self::SharedDict(v) => &v.prefix, } } } impl StagedOptScalar { /// Build from optional values: dense non-null entries in `values`, /// per-feature flags in `presence`. #[must_use] pub fn from_optional( name: impl Into, values: impl IntoIterator>, ) -> Self { let values = values.into_iter(); let (lower, upper) = values.size_hint(); let capacity = upper.unwrap_or(lower); let mut presence = Vec::with_capacity(capacity); let mut dense = Vec::with_capacity(capacity); for value in values { presence.push(value.is_some()); if let Some(value) = value { dense.push(value); } } Self::from_parts(name, presence, dense) } #[must_use] pub(crate) fn from_parts(name: impl Into, presence: Vec, values: Vec) -> Self { Self { name: name.into(), presence, values, } } } ================================================ FILE: rust/mlt-core/src/encoder/property/mod.rs ================================================ pub(crate) mod encode; mod model; mod shared_dict; mod strings; #[cfg(test)] mod tests; pub use model::{ StagedOptScalar, StagedProperty, StagedScalar, StagedSharedDict, StagedSharedDictItem, StagedStrings, }; ================================================ FILE: rust/mlt-core/src/encoder/property/model.rs ================================================ use crate::DictRange; /// Staged property column (encode-side, fully owned). /// /// Unlike `ParsedProperty` (decode-side, potentially borrowed), all string names /// and corpus data are owned strings. No lifetime parameter needed. #[derive(Debug, Clone, PartialEq, strum::IntoStaticStr)] #[strum(serialize_all = "snake_case")] pub enum StagedProperty { Bool(StagedScalar), I8(StagedScalar), U8(StagedScalar), I32(StagedScalar), U32(StagedScalar), I64(StagedScalar), U64(StagedScalar), F32(StagedScalar), F64(StagedScalar), Str(StagedStrings), OptBool(StagedOptScalar), OptI8(StagedOptScalar), OptU8(StagedOptScalar), OptI32(StagedOptScalar), OptU32(StagedOptScalar), OptI64(StagedOptScalar), OptU64(StagedOptScalar), OptF32(StagedOptScalar), OptF64(StagedOptScalar), OptStr(StagedStrings), SharedDict(StagedSharedDict), } /// Owned non-optional scalar column prepared for encoding (bool, integer, or float). /// /// Every feature in this column has a value; there are no nulls. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(test, derive(proptest_derive::Arbitrary))] pub struct StagedScalar { pub(crate) name: String, pub values: Vec, } /// Owned optional scalar column prepared for encoding (bool, integer, or float). /// /// `values` contains only the non-null (present) values in dense order. /// `presence[i]` is `true` when feature `i` has a value; the corresponding dense /// entry is `values[k]` where `k` is the count of `true` entries before index `i`. #[derive(Debug, Clone, PartialEq)] pub struct StagedOptScalar { pub(crate) name: String, pub presence: Vec, pub values: Vec, } /// Owned non-optional string column prepared for encoding. #[derive(Debug, Clone, PartialEq, Eq)] pub struct StagedStrings { pub(crate) name: String, /// Per-feature cumulative end offsets into `data` (same encoding as `ParsedStrings::lengths`). pub lengths: Vec, pub(crate) data: String, } /// Owned shared-dictionary column prepared for encoding. #[derive(Debug, Clone, PartialEq, Eq)] pub struct StagedSharedDict { pub(crate) prefix: String, pub(crate) data: String, pub items: Vec, } /// A single child within a staged shared-dictionary column. #[derive(Debug, Clone, PartialEq, Eq)] pub struct StagedSharedDictItem { pub(crate) suffix: String, /// Per-feature byte ranges into the shared corpus. pub ranges: Vec, /// It's OK to write unneeded one, but can't be false with nulls. pub(crate) has_presence: bool, } ================================================ FILE: rust/mlt-core/src/encoder/property/shared_dict.rs ================================================ //! Optimizer that groups string columns into shared dictionaries using `MinHash` //! similarity, then hands off to per-column auto-encoders. use std::collections::HashMap; use integer_encoding::VarIntWriter as _; use probabilistic_collections::SipHasherBuilder; use probabilistic_collections::similarity::MinHash; use union_find::{QuickUnionUf, UnionBySize, UnionFind as _}; use crate::MltError::DictIndexOutOfBounds; use crate::codecs::fsst::compress_fsst_with; use crate::decoder::strings::{decode_shared_dict_range, encode_shared_dict_range}; use crate::decoder::{PropValue, TileLayer}; use crate::encoder::model::{StrEncoding, StreamCtx}; use crate::encoder::optimizer::{Presence, PropertyStats, SharedDictRole}; use crate::encoder::property::strings::{fsst_try_train, write_fsst_data, write_raw_str_data}; use crate::encoder::{Codecs, Encoder, StagedSharedDict, StagedSharedDictItem}; use crate::errors::AsMltError as _; use crate::utils::{AsUsize as _, checked_sum3, strings_to_lengths}; use crate::{ColumnType, DictRange, DictionaryType, LengthType, MltResult, OffsetType, StreamType}; /// Number of [`MinHash`] permutations. 128 gives ~9 % error on Jaccard estimates. const MINHASH_PERMUTATIONS: usize = 128; /// String columns whose estimated Jaccard similarity exceeds this threshold are /// grouped into a single shared dictionary. const MINHASH_SIMILARITY_THRESHOLD: f64 = 0.075; struct StringProfile<'a> { col_idx: usize, name: &'a str, /// `MinHash` over exact string values. exact_hashes: Vec, /// `MinHash` over byte trigrams (empty when all strings are shorter than 3 bytes). trigram_hashes: Vec, } impl TileLayer { /// Compute which string columns can be merged into a shared dict. #[hotpath::measure] pub(crate) fn group_string_properties(&self, properties: &mut [PropertyStats]) { let exact_mh = MinHash::with_hashers( MINHASH_PERMUTATIONS, [ SipHasherBuilder::from_seed(0, 0), SipHasherBuilder::from_seed(1, 1), ], ); let trigram_mh = MinHash::with_hashers( MINHASH_PERMUTATIONS, [ SipHasherBuilder::from_seed(0, 0), SipHasherBuilder::from_seed(1, 1), ], ); let profiles: Vec> = self .property_names .iter() .enumerate() .filter_map(|(col_idx, name)| { let vals: Vec<&str> = self .features .iter() .filter_map(|f| match f.properties.get(col_idx) { Some(PropValue::Str(Some(s))) => Some(s.as_str()), _ => None, }) .collect(); if vals.is_empty() { return None; } let exact_hashes = exact_mh.get_min_hashes(vals.iter().copied()); let trigrams: Vec<[u8; 3]> = vals .iter() .flat_map(|s| s.as_bytes().windows(3).map(|w| [w[0], w[1], w[2]])) .collect(); let trigram_hashes = if trigrams.is_empty() { Vec::new() } else { trigram_mh.get_min_hashes(trigrams.into_iter()) }; Some(StringProfile { col_idx, name, exact_hashes, trigram_hashes, }) }) .collect(); for group in cluster_by_similarity(profiles) { debug_assert!( group .iter() .all(|p| properties[p.col_idx].presence != Presence::AllNull) ); let owner_col = group[0].col_idx; properties[owner_col] .stats .set_shared_dict(SharedDictRole::Owner(common_prefix_name(&group))); for profile in group.iter().skip(1) { properties[profile.col_idx] .stats .set_shared_dict(SharedDictRole::Member(owner_col)); } } } } /// Estimate Jaccard similarity from two `MinHash` signature vectors. #[allow(clippy::cast_precision_loss)] fn minhash_similarity(a: &[u64], b: &[u64]) -> f64 { if a.is_empty() || b.is_empty() { return 0.0; } let matches = a.iter().zip(b).filter(|(x, y)| x == y).count(); matches as f64 / a.len() as f64 } fn cluster_by_similarity(profiles: Vec>) -> Vec>> { if profiles.is_empty() { return Vec::new(); } let n = profiles.len(); let mut uf = QuickUnionUf::::new(n); for i in 0..n { for j in (i + 1)..n { let exact = minhash_similarity(&profiles[i].exact_hashes, &profiles[j].exact_hashes); let tri = minhash_similarity(&profiles[i].trigram_hashes, &profiles[j].trigram_hashes); if f64::max(exact, tri) > MINHASH_SIMILARITY_THRESHOLD { uf.union(i, j); } } } let mut groups_map = HashMap::>>::new(); for (i, profile) in profiles.into_iter().enumerate() { let root = uf.find(i); groups_map.entry(root).or_default().push(profile); } let mut groups: Vec>> = groups_map .into_values() .filter_map(|mut v| { if v.len() >= 2 { v.sort_unstable_by_key(|p| p.col_idx); Some(v) } else { None } }) .collect(); groups.sort_unstable_by_key(|g| g[0].col_idx); groups } /// Returns the longest common byte prefix of `names`. fn common_prefix_name(profiles: &[StringProfile<'_>]) -> String { debug_assert!(!profiles.is_empty()); let first = profiles[0].name; let mut prefix_len = first.len(); for p in &profiles[1..] { let new_len = first .chars() .zip(p.name.chars()) .take_while(|(a, b)| a == b) .count(); prefix_len = prefix_len.min(new_len); if prefix_len == 0 { return String::new(); } } first[..first.floor_char_boundary(prefix_len)].to_owned() } impl StagedSharedDict { #[must_use] pub fn corpus(&self) -> &str { &self.data } #[must_use] pub fn get(&self, span: (u32, u32)) -> Option<&str> { self.corpus().get(span.0.as_usize()..span.1.as_usize()) } } pub fn collect_staged_shared_dict_spans(items: &[StagedSharedDictItem]) -> Vec<(u32, u32)> { let mut spans = items .iter() .flat_map(StagedSharedDictItem::dense_spans) .collect::>(); spans.sort_unstable(); spans.dedup(); spans } impl StagedSharedDictItem { #[must_use] pub fn feature_count(&self) -> usize { self.ranges.len() } pub fn has_presence(&self) -> bool { self.has_presence } #[cfg(feature = "__private")] pub fn set_presence(&mut self, value: bool) { self.has_presence = value; } pub fn presence_bools(&self) -> impl ExactSizeIterator + '_ { self.ranges .iter() .map(|&range| decode_shared_dict_range(range).is_some()) } pub fn dense_spans(&self) -> impl Iterator + '_ { self.ranges .iter() .filter_map(|&range| decode_shared_dict_range(range)) } } impl StagedSharedDict { /// Build a shared-dictionary column directly from raw per-column string data. /// /// Each column is a `(suffix, values, presence)` tuple where `values` is an iterator /// of optional strings (one per feature). All unique non-null strings across every /// column are deduplicated into a shared byte corpus; per-feature byte-range offsets /// into that corpus are recorded in each shared-dictionary item. pub fn new( prefix: impl Into, columns: impl IntoIterator, ) -> MltResult where S: Into, I: IntoIterator>, T: AsRef, { let prefix = prefix.into(); let mut dict_index = HashMap::::new(); let mut data = String::new(); let items = columns .into_iter() .map( |(suffix, values, presence)| -> MltResult { let values = values.into_iter(); let (lower, upper) = values.size_hint(); let mut ranges = Vec::with_capacity(upper.unwrap_or(lower)); for opt_val in values { match opt_val { Some(value) => { let s = value.as_ref(); let (start, end) = if let Some(&span) = dict_index.get(s) { span } else { let start = u32::try_from(data.len()).or_overflow()?; let end = start .checked_add(u32::try_from(s.len()).or_overflow()?) .or_overflow()?; data.push_str(s); dict_index.insert(s.to_owned(), (start, end)); (start, end) }; ranges.push(encode_shared_dict_range(start, end)?); } None => ranges.push(DictRange::NULL), } } Ok(StagedSharedDictItem { suffix: suffix.into(), ranges, has_presence: presence != Presence::AllPresent, }) }, ) .collect::, _>>()?; Ok(Self { prefix, data, items, }) } } impl Codecs { /// Encode a shared-dictionary property and write it to `enc`. /// /// When [`Encoder::override_str_enc`] returns [`None`], auto-selects the corpus encoding (FSST if viable, else plain) /// and uses automatic offset encoders. /// When [`Some`], uses the caller-specified encoding and [`Encoder::override_int_enc`] for offsets. /// /// The caller (staging) is responsible for not creating empty `StagedSharedDict` instances. #[hotpath::measure] pub(crate) fn write_shared_dict( &mut self, shared_dict: &StagedSharedDict, enc: &mut Encoder, ) -> MltResult<()> { let dict_spans = collect_staged_shared_dict_spans(&shared_dict.items); let dict: Vec<&str> = dict_spans .iter() .map(|&span| { shared_dict .get(span) .ok_or(DictIndexOutOfBounds(span.0, dict_spans.len())) }) .collect::>()?; let dict_index: HashMap<(u32, u32), u32> = dict_spans.iter().copied().zip(0_u32..).collect(); // Decide corpus encoding upfront to determine the stream count for the varint header. // FSST uses 4 streams; plain uses 2. let str_enc_override = enc.override_str_enc(&shared_dict.prefix); let fsst_raw = match str_enc_override { Some(StrEncoding::Fsst | StrEncoding::FsstDict) => { let byte_slices: Vec<&[u8]> = dict.iter().map(|s| s.as_bytes()).collect(); let compressor = fsst::Compressor::train(&byte_slices); Some(compress_fsst_with(&dict, &compressor)) } Some(StrEncoding::Plain | StrEncoding::Dict) => None, None => { // Populate cache on first sort trial, reuse on subsequent. // Key includes the suffix as otherwise multiple groups could share the same prefix // (e.g. two "name:" groups for Arabic vs Cyrillic scripts). // Since the grouping is done only once, the order inside the items is deterministic, so we can just take the first suffix for the cache key. let first_suffix = shared_dict.items.first().map_or("", |i| &i.suffix); enc.fsst_cache .entry(format!( "{prefix}{first_suffix}", prefix = shared_dict.prefix )) .or_insert_with(|| fsst_try_train(&dict)) .as_ref() .map(|c| compress_fsst_with(&dict, c)) } }; let dict_stream_count = if fsst_raw.is_some() { 4u32 } else { 2u32 }; let children_count = u32::try_from(shared_dict.items.len())?; let optional_count = u32::try_from( shared_dict .items .iter() .filter(|p| p.has_presence()) .count(), )?; let stream_len = checked_sum3(dict_stream_count, children_count, optional_count)?; // Write stream data: total count, corpus streams, then per-child streams. enc.write_varint(stream_len)?; if let Some(ref raw) = fsst_raw { write_fsst_data(raw, DictionaryType::Single, &shared_dict.prefix, enc, self)?; } else { let lengths = strings_to_lengths(&dict)?; let typ = StreamType::Length(LengthType::Dictionary); let ctx = StreamCtx::prop(typ, &shared_dict.prefix); self.write_int_stream(&lengths, &ctx, enc)?; write_raw_str_data(&dict, DictionaryType::Shared, enc)?; } enc.write_column_header(ColumnType::SharedDict, &shared_dict.prefix)?; enc.meta.write_varint(children_count)?; for item in &shared_dict.items { if item.has_presence() { enc.write_varint(2u32)?; enc.write_column_type(ColumnType::OptStr)?; self.write_presence_stream(item.presence_bools(), enc)?; } else { enc.write_varint(1u32)?; enc.write_column_type(ColumnType::Str)?; } enc.write_column_name(&item.suffix)?; let offsets: Vec = item .dense_spans() .map(|span| { dict_index .get(&span) .copied() .ok_or(DictIndexOutOfBounds(span.0, dict_spans.len())) }) .collect::>()?; let typ = StreamType::Offset(OffsetType::String); let ctx = StreamCtx::prop2(typ, &shared_dict.prefix, &item.suffix); self.write_int_stream(&offsets, &ctx, enc)?; } Ok(()) } } ================================================ FILE: rust/mlt-core/src/encoder/property/strings.rs ================================================ use fsst::Compressor; use integer_encoding::VarIntWriter as _; use super::model::StagedStrings; use crate::MltResult; use crate::codecs::fsst::{FsstRawData, compress_fsst, compress_fsst_with}; use crate::decoder::strings::{checked_string_end, encode_null_end}; use crate::decoder::{DictionaryType, LengthType, OffsetType, StreamMeta, StreamType}; use crate::encoder::model::{StrEncoding, StreamCtx}; use crate::encoder::stream::{dedup_strings, write_stream_payload}; use crate::encoder::{Codecs, Encoder}; use crate::utils::{AsUsize as _, strings_to_lengths}; /// Minimum total raw byte size of a column before attempting FSST compression. const FSST_OVERHEAD_THRESHOLD: usize = 2_048; /// Maximum number of strings sampled for the FSST viability probe. const FSST_SAMPLE_STRINGS: usize = 256; /// Train an FSST compressor and return it when compression is likely to save space. /// /// Returns `None` when the column is empty, too small for FSST overhead to pay off, /// or when trial compression shows no benefit. /// /// Training always uses all values so the symbol table sees the full distribution. /// The viability probe (trial compression) is limited to [`FSST_SAMPLE_STRINGS`] to /// bound cost. #[hotpath::measure] pub(crate) fn fsst_try_train(strings: &[&str]) -> Option { if strings.is_empty() { return None; } let total_plain_size: usize = strings.iter().map(|s| s.len()).sum(); if total_plain_size < FSST_OVERHEAD_THRESHOLD { return None; } let byte_slices: Vec<&[u8]> = strings.iter().map(|s| s.as_bytes()).collect(); let compressor = Compressor::train(&byte_slices); let symbols = compressor.symbol_table(); let symbol_lengths = compressor.symbol_lengths(); let symbol_overhead: usize = symbol_lengths .iter() .take(symbols.len()) .map(|&l| usize::from(l)) .sum(); let sample = if strings.len() <= FSST_SAMPLE_STRINGS { strings } else { &strings[..FSST_SAMPLE_STRINGS] }; let plain_size: usize = sample.iter().map(|s| s.len()).sum(); let compressed_size: usize = sample .iter() .map(|s| compressor.compress(s.as_bytes()).len()) .sum(); if symbol_overhead + compressed_size < plain_size { Some(compressor) } else { None } } impl Codecs { /// Encode a string column, following the same explicit-or-auto pattern as numeric columns. /// /// If [`Encoder::override_str_enc`] returns `Some`, only that type is encoded. /// Otherwise Plain, Dict, and (when viable) FSST variants are competed via the alternatives /// machinery, mirroring the `write_int_prop_*` pattern one level up. #[hotpath::measure] pub(crate) fn write_str_col( &mut self, v: &StagedStrings, presence: Option<&StagedStrings>, enc: &mut Encoder, ) -> MltResult<()> { let non_null = v.dense_values(); let name = &v.name; if let Some(str_enc) = enc.override_str_enc(name) { match str_enc { StrEncoding::Plain => write_str_plain(&non_null, presence, name, enc, self)?, StrEncoding::Dict => write_str_dict(&non_null, presence, name, enc, self)?, StrEncoding::Fsst => write_str_fsst(&non_null, presence, name, enc, self)?, StrEncoding::FsstDict => write_str_fsst_dict(&non_null, presence, name, enc, self)?, } } else { // Dedup once; reused by Dict and FSST+Dict alternatives. let (unique, offset_indices) = dedup_strings(&non_null)?; // Train on deduplicated values once; cached across sort trials. let compressor = enc .fsst_cache .entry(name.clone()) .or_insert_with(|| fsst_try_train(&unique)); // Pre-compute compressed data while cache is accessible (before try_alternatives // borrows enc). The FsstRawData is owned, so the cache borrow ends here. let count = non_null.len(); let plain_fsst = compressor .as_ref() .map(|c| compress_fsst_with(&non_null, c)); let dict_fsst = compressor.as_ref().map(|c| compress_fsst_with(&unique, c)); let mut alt = enc.try_alternatives(); alt.with(|enc| write_str_plain(&non_null, presence, name, enc, self))?; alt.with(|enc| { write_str_dict_raw(&unique, &offset_indices, presence, name, enc, self) })?; if let Some(ref raw) = plain_fsst { alt.with(|enc| write_str_fsst_raw(raw, count, presence, name, enc, self))?; } if let Some(ref raw) = dict_fsst { alt.with(|enc| { write_str_fsst_dict_raw(raw, &offset_indices, presence, name, enc, self) })?; } } Ok(()) } } /// Encode with plain (`VarBinary` lengths) layout. /// /// Stream count varint is written first, then presence, then the lengths stream /// (via [`Codecs::write_int_stream`] which handles the explicit/auto dispatch internally), /// then the raw string bytes as a plain unencoded data stream. #[hotpath::measure] fn write_str_plain( non_null: &[&str], presence: Option<&StagedStrings>, name: &str, enc: &mut Encoder, codecs: &mut Codecs, ) -> MltResult<()> { let lengths = strings_to_lengths(non_null)?; enc.write_varint(2u32 + u32::from(presence.is_some()))?; write_presence_stream(presence, enc, codecs)?; let ctx = StreamCtx::prop(StreamType::Length(LengthType::VarBinary), name); codecs.write_int_stream(&lengths, &ctx, enc)?; write_raw_str_data(non_null, DictionaryType::None, enc) } /// Encode with dictionary (deduped corpus + offset indices) layout. #[hotpath::measure] fn write_str_dict( non_null: &[&str], presence: Option<&StagedStrings>, name: &str, enc: &mut Encoder, codecs: &mut Codecs, ) -> MltResult<()> { let (unique, offset_indices) = dedup_strings(non_null)?; write_str_dict_raw(&unique, &offset_indices, presence, name, enc, codecs) } /// Write pre-deduped dictionary data. fn write_str_dict_raw( unique: &[&str], offset_indices: &[u32], presence: Option<&StagedStrings>, name: &str, enc: &mut Encoder, codecs: &mut Codecs, ) -> MltResult<()> { let lengths = strings_to_lengths(unique)?; enc.write_varint(3u32 + u32::from(presence.is_some()))?; write_presence_stream(presence, enc, codecs)?; let ctx = StreamCtx::prop(StreamType::Length(LengthType::Dictionary), name); codecs.write_int_stream(&lengths, &ctx, enc)?; let ctx = StreamCtx::prop(StreamType::Offset(OffsetType::String), name); codecs.write_int_stream(offset_indices, &ctx, enc)?; write_raw_str_data(unique, DictionaryType::Single, enc) } /// Encode with FSST compression, training a fresh compressor. /// /// Used by the explicit-encoder path. #[hotpath::measure] fn write_str_fsst( non_null: &[&str], presence: Option<&StagedStrings>, name: &str, enc: &mut Encoder, codecs: &mut Codecs, ) -> MltResult<()> { let raw = compress_fsst(non_null); write_str_fsst_raw(&raw, non_null.len(), presence, name, enc, codecs) } /// Shared FSST write logic. fn write_str_fsst_raw( raw: &FsstRawData, count: usize, presence: Option<&StagedStrings>, name: &str, enc: &mut Encoder, codecs: &mut Codecs, ) -> MltResult<()> { let offsets: Vec = (0..u32::try_from(count)?).collect(); enc.write_varint(5u32 + u32::from(presence.is_some()))?; write_presence_stream(presence, enc, codecs)?; write_fsst_data(raw, DictionaryType::Single, name, enc, codecs)?; let ctx = StreamCtx::prop(StreamType::Offset(OffsetType::String), name); codecs.write_int_stream(&offsets, &ctx, enc) } /// Encode with FSST + dictionary layout, training a fresh compressor. /// /// Used by the explicit-encoder path. #[hotpath::measure] fn write_str_fsst_dict( non_null: &[&str], presence: Option<&StagedStrings>, name: &str, enc: &mut Encoder, codecs: &mut Codecs, ) -> MltResult<()> { let (unique, offset_indices) = dedup_strings(non_null)?; let raw = compress_fsst(&unique); write_str_fsst_dict_raw(&raw, &offset_indices, presence, name, enc, codecs) } /// Shared FSST+dict write logic. fn write_str_fsst_dict_raw( raw: &FsstRawData, offset_indices: &[u32], presence: Option<&StagedStrings>, name: &str, enc: &mut Encoder, codecs: &mut Codecs, ) -> MltResult<()> { enc.write_varint(5u32 + u32::from(presence.is_some()))?; write_presence_stream(presence, enc, codecs)?; write_fsst_data(raw, DictionaryType::Single, name, enc, codecs)?; let ctx = StreamCtx::prop(StreamType::Offset(OffsetType::String), name); codecs.write_int_stream(offset_indices, &ctx, enc) } fn write_presence_stream( presence: Option<&StagedStrings>, enc: &mut Encoder, codecs: &mut Codecs, ) -> MltResult<()> { if let Some(strings) = presence { codecs.write_presence_stream(strings.presence_bools(), enc)?; } Ok(()) } /// Write 4 FSST sub-streams directly to `enc.data`. /// /// The two integer sub-streams (`symbol_lengths`, `value_lengths`) use [`Codecs::write_int_stream`] /// so explicit encoder overrides are honored and all candidates are competed automatically. /// The two raw-byte sub-streams (`symbol_table`, `corpus`) are written without integer encoding. /// /// Stream order: `symbol_lengths`, `symbol_table`, `value_lengths`, `corpus`. #[hotpath::measure] pub fn write_fsst_data( raw: &FsstRawData, dict_type: DictionaryType, name: &str, enc: &mut Encoder, codecs: &mut Codecs, ) -> MltResult<()> { let ctx = StreamCtx::prop(StreamType::Length(LengthType::Symbol), name); codecs.write_int_stream(&raw.symbol_lengths, &ctx, enc)?; let typ = StreamType::Data(DictionaryType::Fsst); let meta = StreamMeta::new_none(typ, raw.symbol_lengths.len())?; write_stream_payload(&mut enc.data, meta, false, &raw.symbol_bytes)?; let ctx = StreamCtx::prop(StreamType::Length(LengthType::Dictionary), name); codecs.write_int_stream(&raw.value_lengths, &ctx, enc)?; let meta = StreamMeta::new_none(StreamType::Data(dict_type), raw.value_lengths.len())?; write_stream_payload(&mut enc.data, meta, false, &raw.corpus)?; Ok(()) } /// Write raw string bytes as an unencoded data stream directly to `enc.data`. #[hotpath::measure] pub fn write_raw_str_data( strings: &[&str], dict_type: DictionaryType, enc: &mut Encoder, ) -> MltResult<()> { let total_len: usize = strings.iter().map(|s| s.len()).sum(); let typ = StreamType::Data(dict_type); let meta = StreamMeta::new_none(typ, strings.len())?; meta.write_to(enc, false, u32::try_from(total_len)?)?; enc.data.reserve(total_len); for s in strings { enc.data.extend_from_slice(s.as_bytes()); } Ok(()) } impl StagedStrings { /// Stages a string column where every row has a value (no nulls). /// /// `name` is the column key (e.g. shared-dict suffix or top-level property name). /// /// `values` can be any iterator of string fragments, for example `["a", "b"]`, /// `vec!["x".into(), "y".into()]`, or `some_vec.iter().map(|s| s.as_str())`. #[must_use] pub fn from_strings( name: impl Into, values: impl IntoIterator>, ) -> Self { let name = name.into(); let iter = values.into_iter(); let (lower, _) = iter.size_hint(); let mut lengths = Vec::with_capacity(lower); let mut data = String::new(); let mut end = 0_i32; for value in iter { let value = value.as_ref(); end = checked_string_end(end, value.len()) .expect("staged string corpus exceeds supported i32 range"); lengths.push(end); data.push_str(value); } Self { name, lengths, data, } } /// Stages a string column with optional values (nulls encoded in the length stream). /// /// `name` is the column key (e.g. shared-dict suffix or top-level property name). /// /// `values` can be any iterator of optional string fragments, for example /// `vec![Some("a"), None]` or a `Vec>`. #[must_use] pub fn from_optional( name: impl Into, values: impl IntoIterator>>, ) -> Self { let name = name.into(); let iter = values.into_iter(); let (lower, _) = iter.size_hint(); let mut lengths = Vec::with_capacity(lower); let mut data = String::new(); let mut end = 0_i32; for value in iter { match value { Some(value) => { let value = value.as_ref(); end = checked_string_end(end, value.len()) .expect("staged string corpus exceeds supported i32 range"); lengths.push(end); data.push_str(value); } None => lengths.push(encode_null_end(end)), } } Self { name, lengths, data, } } #[must_use] pub fn feature_count(&self) -> usize { self.lengths.len() } pub fn presence_bools(&self) -> impl ExactSizeIterator + '_ { self.lengths.iter().map(|&end| end >= 0) } #[must_use] pub fn dense_values(&self) -> Vec<&str> { let mut values = Vec::new(); let mut start = 0_u32; for &end in &self.lengths { if end >= 0 { let end = end.cast_unsigned(); values.push(&self.data[start.as_usize()..end.as_usize()]); start = end; } else { start = (!end).cast_unsigned(); } } values } } ================================================ FILE: rust/mlt-core/src/encoder/property/tests.rs ================================================ use geo_types::Point; use proptest::prelude::*; use rstest::rstest; use crate::encoder::SortStrategy::Unsorted; use crate::encoder::model::{ExplicitEncoder, StagedLayer, StrEncoding}; use crate::encoder::optimizer::{Presence, PropertyTypedStats, SharedDictRole}; use crate::encoder::property::encode::write_properties; use crate::encoder::{ Codecs, Encoder, EncoderConfig, IntEncoder, LogicalEncoder, PhysicalEncoder, StagedId, StagedProperty, StagedSharedDict, stage_tile, }; use crate::test_helpers::{dec, parser}; use crate::{DictRange, GeometryValues, Layer, MltError, PropValue, TileFeature, TileLayer}; // proptest_derive::Arbitrary is only derived for these types inside the crate // under #[cfg(test)], so we write the strategies by hand here. fn arb_logical_encoder() -> impl Strategy { prop_oneof![ Just(LogicalEncoder::None), Just(LogicalEncoder::Delta), Just(LogicalEncoder::DeltaRle), Just(LogicalEncoder::Rle), ] } fn arb_physical_encoder() -> impl Strategy { prop_oneof![ Just(PhysicalEncoder::None), Just(PhysicalEncoder::VarInt), Just(PhysicalEncoder::FastPFOR), ] } /// [`PhysicalEncoder`] variants that are valid for 64-bit integers /// (i.e. everything except `FastPFOR`). fn arb_physical_no_fastpfor() -> impl Strategy { prop_oneof![Just(PhysicalEncoder::None), Just(PhysicalEncoder::VarInt),] } fn arb_int_encoder() -> impl Strategy { (arb_logical_encoder(), arb_physical_encoder()) .prop_map(|(logical, physical)| IntEncoder::new(logical, physical)) } /// [`IntEncoder`] strategy that excludes `FastPFOR`, which only handles 32-bit integers. fn arb_int_encoder_no_fastpfor() -> impl Strategy { (arb_logical_encoder(), arb_physical_no_fastpfor()) .prop_map(|(logical, physical)| IntEncoder::new(logical, physical)) } fn staged_len(staged: &StagedProperty) -> usize { match staged { StagedProperty::Bool(s) => s.values.len(), StagedProperty::I8(s) => s.values.len(), StagedProperty::U8(s) => s.values.len(), StagedProperty::I32(s) => s.values.len(), StagedProperty::U32(s) => s.values.len(), StagedProperty::I64(s) => s.values.len(), StagedProperty::U64(s) => s.values.len(), StagedProperty::F32(s) => s.values.len(), StagedProperty::F64(s) => s.values.len(), StagedProperty::OptBool(s) => s.presence.len(), StagedProperty::OptI8(s) => s.presence.len(), StagedProperty::OptU8(s) => s.presence.len(), StagedProperty::OptI32(s) => s.presence.len(), StagedProperty::OptU32(s) => s.presence.len(), StagedProperty::OptI64(s) => s.presence.len(), StagedProperty::OptU64(s) => s.presence.len(), StagedProperty::OptF32(s) => s.presence.len(), StagedProperty::OptF64(s) => s.presence.len(), StagedProperty::Str(s) | StagedProperty::OptStr(s) => s.lengths.len(), StagedProperty::SharedDict(s) => s.items.first().map_or(0, |i| i.ranges.len()), } } fn strs(vals: &[&str]) -> Vec> { vals.iter().map(|v| Some((*v).to_string())).collect() } fn opt_strs(vals: &[Option<&str>]) -> Vec> { vals.iter().map(|v| v.map(ToString::to_string)).collect() } fn presence(values: &[Option]) -> Presence { if values.iter().all(Option::is_some) { Presence::AllPresent } else { Presence::Mixed } } fn shared_dict_prop(name: &str, children: Vec<(String, Vec>)>) -> StagedProperty { let children = children.into_iter().map(|(suffix, values)| { let presence = presence(&values); (suffix, values, presence) }); StagedProperty::SharedDict(StagedSharedDict::new(name, children).expect("build shared dict")) } type SharedDictChildren = Vec<(String, Vec>)>; fn arb_shared_dict_children() -> impl Strategy { (1usize..20, 1usize..5usize).prop_flat_map(|(n, child_count)| { prop::collection::vec( ( "[a-z]{1,6}", prop::collection::vec(prop::option::of("[a-zA-Z ]{0,20}"), n), ), child_count, ) .prop_map(move |mut children| { if children .iter() .all(|(_, vals)| vals.iter().all(Option::is_none)) { children[0].1[0] = Some(String::new()); } (n, children) }) }) } /// Build a `(name, values)` pair for use as a [`shared_dict_prop`] column. fn col(name: &str, values: Vec>) -> (String, Vec>) { (name.to_string(), values) } /// Shorthand for a non-null [`PropValue::Str`]. fn ps(s: &str) -> PropValue { PropValue::Str(Some(s.into())) } /// Create a [`GeometryValues`] with `n` degenerate point features at the origin. fn n_point_geometry(n: usize) -> GeometryValues { let mut g = GeometryValues::default(); for _ in 0..n { g.push_geom(&geo_types::Geometry::::Point(Point::new(0, 0))); } g } /// Encode `props` as a layer with matching point geometry and return the raw bytes. fn encode_to_bytes(props: Vec) -> Vec { encode_to_bytes_explicit(props, ExplicitEncoder::all(IntEncoder::varint())) } /// Encode `props` with explicit encoder config and return the raw bytes. fn encode_to_bytes_explicit(props: Vec, cfg: ExplicitEncoder) -> Vec { let n = props.iter().map(staged_len).max().unwrap_or(0); let layer = StagedLayer { name: "test".into(), extent: 4096, id: StagedId::None, geometry: n_point_geometry(n), properties: props, }; let enc = Encoder::with_explicit(EncoderConfig::default(), cfg); let mut codecs = Codecs::default(); let enc = layer .encode_into(enc, &mut codecs) .expect("encoding failed"); enc.into_layer_bytes().expect("into_layer_bytes failed") } /// Encode and immediately decode `props` into a [`TileLayer`] using auto varint encoding. fn encode_and_tile(props: Vec) -> TileLayer { encode_and_tile_explicit(props, ExplicitEncoder::all(IntEncoder::varint())) } /// Encode and decode with explicit encoder config. fn encode_and_tile_explicit(props: Vec, cfg: ExplicitEncoder) -> TileLayer { let bytes = encode_to_bytes_explicit(props, cfg); let (_, layer) = Layer::from_bytes(&bytes, &mut parser()).expect("layer parse failed"); let Layer::Tag01(layer01) = layer else { panic!("expected Tag01 layer") }; let mut d = dec(); let parsed = layer01.decode_all(&mut d).expect("decode failed"); parsed.into_tile(&mut d).expect("into_tile failed") } // Absent mode has no presence stream on the wire, so only all-Some inputs are // valid for those variants. macro_rules! integer_roundtrip_proptests { ($present:ident, $absent:ident, $variant:ident, $opt_fn:ident, $non_opt_fn:ident, $ty:ty, $int_encoder:expr) => { proptest! { #[test] fn $present( values in prop::collection::vec(prop::option::of(any::<$ty>()), 1..100), enc in $int_encoder, ) { // All-null columns are skipped in encoding; only test when at // least one value is present. prop_assume!(values.iter().any(Option::is_some)); let tile = encode_and_tile_explicit( vec![StagedProperty::$opt_fn("x", values.clone())], ExplicitEncoder::all(enc), ); prop_assert_eq!(&tile.property_names, &["x"]); for (i, ov) in values.into_iter().enumerate() { prop_assert_eq!(&tile.features[i].properties[0], &PropValue::$variant(ov)); } } #[test] fn $absent( values in prop::collection::vec(any::<$ty>(), 1..100), enc in $int_encoder, ) { let tile = encode_and_tile_explicit( vec![StagedProperty::$non_opt_fn("x", values.clone())], ExplicitEncoder::all(enc), ); prop_assert_eq!(&tile.property_names, &["x"]); for (i, &v) in values.iter().enumerate() { prop_assert_eq!(&tile.features[i].properties[0], &PropValue::$variant(Some(v))); } } } }; } // i8, u8, i32, u32 — all physical encoders are valid. integer_roundtrip_proptests!(i8_present, i8_absent, I8, opt_i8, i8, i8, arb_int_encoder()); integer_roundtrip_proptests!(u8_present, u8_absent, U8, opt_u8, u8, u8, arb_int_encoder()); integer_roundtrip_proptests!( i32_present, i32_absent, I32, opt_i32, i32, i32, arb_int_encoder() ); integer_roundtrip_proptests!( u32_present, u32_absent, U32, opt_u32, u32, u32, arb_int_encoder() ); // FastPFOR does not support 64-bit integers. integer_roundtrip_proptests!( i64_present, i64_absent, I64, opt_i64, i64, i64, arb_int_encoder_no_fastpfor() ); integer_roundtrip_proptests!( u64_present, u64_absent, U64, opt_u64, u64, u64, arb_int_encoder_no_fastpfor() ); #[test] fn bool_specific_values() { let values = vec![Some(true), None, Some(false), Some(true), None]; let tile = encode_and_tile(vec![StagedProperty::opt_bool("active", values.clone())]); assert_eq!(tile.property_names, vec!["active"]); for (i, ov) in values.into_iter().enumerate() { assert_eq!(&tile.features[i].properties[0], &PropValue::Bool(ov)); } } proptest! { #[test] fn bool_roundtrip( values in prop::collection::vec(prop::option::of(any::()), 1..100), ) { // All-null columns are skipped; only test when at least one value is present. prop_assume!(values.iter().any(Option::is_some)); let tile = encode_and_tile(vec![StagedProperty::opt_bool("flag", values.clone())]); prop_assert_eq!(&tile.property_names, &["flag"]); for (i, ov) in values.into_iter().enumerate() { prop_assert_eq!(&tile.features[i].properties[0], &PropValue::Bool(ov)); } } } // NaN is excluded because NaN != NaN. proptest! { #[test] fn f32_roundtrip( values in prop::collection::vec( prop::option::of(any::().prop_filter("no NaN", |f| !f.is_nan())), 1..100, ), ) { // All-null columns are skipped; only test when at least one value is present. prop_assume!(values.iter().any(Option::is_some)); let tile = encode_and_tile(vec![StagedProperty::opt_f32("score", values.clone())]); prop_assert_eq!(&tile.property_names, &["score"]); for (i, ov) in values.into_iter().enumerate() { prop_assert_eq!(&tile.features[i].properties[0], &PropValue::F32(ov)); } } #[test] fn f64_roundtrip( values in prop::collection::vec( prop::option::of(any::().prop_filter("no NaN", |f| !f.is_nan())), 1..100, ), ) { // All-null columns are skipped; only test when at least one value is present. prop_assume!(values.iter().any(Option::is_some)); let tile = encode_and_tile(vec![StagedProperty::opt_f64("score", values.clone())]); prop_assert_eq!(&tile.property_names, &["score"]); for (i, ov) in values.into_iter().enumerate() { prop_assert_eq!(&tile.features[i].properties[0], &PropValue::F64(ov)); } } } #[test] fn str_scalar_with_nulls() { let values = opt_strs(&[Some("Berlin"), None, Some("Hamburg"), None]); let tile = encode_and_tile(vec![StagedProperty::opt_str("city", values.clone())]); assert_eq!(tile.property_names, vec!["city"]); for (i, ov) in values.into_iter().enumerate() { assert_eq!(&tile.features[i].properties[0], &PropValue::Str(ov)); } } #[test] fn str_scalar_empty() { // Staging an empty column is a no-op at the staging layer (build_scalar_column // never produces empty StagedProperty::str; this test exercises the case where // a tile has zero features — the round-trip must not panic). let tile = encode_and_tile(vec![StagedProperty::str( "unused", std::iter::empty::<&str>(), )]); // Zero features → zero properties should be visible after decoding assert!(tile.features.is_empty()); } proptest! { #[test] fn str_scalar_roundtrip_non_null( values in prop::collection::vec("[a-zA-Z0-9 ]{0,30}", 1..50), ) { let tile = encode_and_tile(vec![StagedProperty::str("name", values.clone())]); prop_assert_eq!(&tile.property_names, &["name"]); for (i, v) in values.into_iter().enumerate() { prop_assert_eq!(&tile.features[i].properties[0], &PropValue::Str(Some(v))); } } #[test] fn str_scalar_roundtrip_with_nulls( values in prop::collection::vec(prop::option::of("[a-zA-Z0-9 ]{0,30}"), 1..50), ) { prop_assume!(values.iter().any(Option::is_some)); let tile = encode_and_tile(vec![StagedProperty::opt_str("name", values.clone())]); prop_assert_eq!(&tile.property_names, &["name"]); for (i, ov) in values.into_iter().enumerate() { prop_assert_eq!(&tile.features[i].properties[0], &PropValue::Str(ov)); } } } #[test] fn fsst_scalar_string_roundtrip() { let values = ["Berlin", "Brandenburg", "Bremen", "Braunschweig"]; let tile = encode_and_tile_explicit( vec![StagedProperty::str("name", values)], ExplicitEncoder::all_with_str(IntEncoder::plain(), StrEncoding::Fsst), ); assert_eq!(tile.property_names, vec!["name"]); for (i, s) in values.iter().enumerate() { assert_eq!( &tile.features[i].properties[0], &PropValue::Str(Some(s.to_string())) ); } } /// Round-trip a two-column `SharedDict` with auto encoders and check all feature values. fn check_two_col_dict( name: &str, s1: &str, vals1: Vec>, s2: &str, vals2: Vec>, ) { let tile = encode_and_tile(vec![shared_dict_prop( name, vec![col(s1, vals1.clone()), col(s2, vals2.clone())], )]); assert_eq!( tile.property_names, vec![format!("{name}{s1}"), format!("{name}{s2}")] ); for (i, (v1, v2)) in vals1.into_iter().zip(vals2).enumerate() { assert_eq!(&tile.features[i].properties[0], &PropValue::Str(v1)); assert_eq!(&tile.features[i].properties[1], &PropValue::Str(v2)); } } #[test] fn fsst_struct_shared_dict_roundtrip() { check_two_col_dict( "name", ":de", strs(&["Berlin", "München", "Köln"]), ":en", strs(&["Berlin", "Munich", "Cologne"]), ); } #[test] fn struct_with_nulls() { check_two_col_dict( "name", ":de", opt_strs(&[Some("Berlin"), Some("München"), None]), ":en", opt_strs(&[Some("Berlin"), None, Some("London")]), ); } #[test] fn struct_shared_dict_inline_ranges_track_nulls_and_empty_strings() { // This test validates internal range bookkeeping in StagedSharedDict — // not the byte encoding pipeline — so it inspects the staged form directly. let dict = StagedSharedDict::new( "name", vec![ ( ":de", opt_strs(&[Some(""), None, Some("Berlin")]), Presence::Mixed, ), ( ":en", opt_strs(&[Some(""), Some("Berlin"), Some("")]), Presence::AllPresent, ), ], ) .unwrap(); let corpus = dict.corpus(); let [de, en] = dict.items.as_slice() else { panic!("expected exactly 2 items"); }; // de: [Some(""), None, Some("Berlin")] assert_ne!(de.ranges[0], DictRange::NULL); assert_eq!(de.ranges[0].start, de.ranges[0].end); // empty string: zero-length span assert_eq!(de.ranges[1], DictRange::NULL); // null entry assert_ne!(de.ranges[2], DictRange::NULL); let start: usize = de.ranges[2].start.try_into().unwrap(); let end: usize = de.ranges[2].end.try_into().unwrap(); assert_eq!(&corpus[start..end], "Berlin"); // en: [Some(""), Some("Berlin"), Some("")] assert_ne!(en.ranges[0], DictRange::NULL); assert_eq!(en.ranges[0].start, en.ranges[0].end); // empty string: zero-length span assert_eq!(en.ranges[1], de.ranges[2]); // same deduped span for "Berlin" assert_ne!(en.ranges[2], DictRange::NULL); assert_eq!(en.ranges[2].start, en.ranges[2].end); // empty string: zero-length span } #[test] fn struct_no_nulls() { check_two_col_dict( "name", ":de", strs(&["Berlin", "München", "Hamburg"]), ":en", strs(&["Berlin", "Munich", "Hamburg"]), ); } #[test] fn struct_shared_dict_deduplication() { check_two_col_dict( "name", ":de", strs(&["Berlin", "Berlin"]), ":en", strs(&["Berlin", "London"]), ); } #[test] fn struct_mixed_with_scalars() { let tile = encode_and_tile(vec![ StagedProperty::opt_u32("population", vec![Some(3_748_000), Some(1_787_000_u32)]), shared_dict_prop( "name:", vec![ col("de", strs(&["Berlin", "Hamburg"])), col("en", strs(&["Berlin", "Hamburg"])), ], ), StagedProperty::opt_u32("rank", vec![Some(1_u32), Some(2)]), ]); assert_eq!( tile.property_names, vec!["population", "name:de", "name:en", "rank"] ); assert_eq!(tile.features.len(), 2); assert_eq!( tile.features[0].properties, vec![ PropValue::U32(Some(3_748_000)), ps("Berlin"), ps("Berlin"), PropValue::U32(Some(1)) ] ); assert_eq!( tile.features[1].properties, vec![ PropValue::U32(Some(1_787_000)), ps("Hamburg"), ps("Hamburg"), PropValue::U32(Some(2)) ] ); } #[test] fn two_struct_groups_with_scalar_between() { let tile = encode_and_tile(vec![ shared_dict_prop( "name:", vec![ col("de", strs(&["Berlin", "Hamburg"])), col("en", strs(&["Berlin", "Hamburg"])), ], ), StagedProperty::opt_u32("population", vec![Some(3_748_000_u32), Some(1_787_000)]), shared_dict_prop( "label:", vec![ col("de", strs(&["BE", "HH"])), col("en", strs(&["BER", "HAM"])), ], ), ]); assert_eq!( tile.property_names, vec!["name:de", "name:en", "population", "label:de", "label:en"] ); assert_eq!(tile.features.len(), 2); assert_eq!( tile.features[0].properties, vec![ ps("Berlin"), ps("Berlin"), PropValue::U32(Some(3_748_000)), ps("BE"), ps("BER") ] ); assert_eq!( tile.features[1].properties, vec![ ps("Hamburg"), ps("Hamburg"), PropValue::U32(Some(1_787_000)), ps("HH"), ps("HAM") ] ); } #[test] fn lazy_layer01_iterate_prop_names_returns_column_names() { // Encode a layer with a scalar column and a two-key SharedDict column. let bytes = encode_to_bytes(vec![ StagedProperty::opt_u32("pop", vec![Some(1_000_u32), Some(2_000)]), shared_dict_prop( "addr:", vec![ col("city", strs(&["Berlin", "Rome"])), col("zip", strs(&["10115", "00100"])), ], ), ]); // Parse as a lazy Layer01 — no column data decoded yet. let (_, layer) = Layer::from_bytes(&bytes, &mut parser()).expect("parse failed"); let Layer::Tag01(layer) = layer else { panic!("expected Tag01 layer") }; let names: Vec = layer.iterate_prop_names().map(|n| n.to_string()).collect(); assert_eq!(names, ["pop", "addr:city", "addr:zip"]); } proptest! { #[test] fn struct_roundtrip( struct_name in "[a-z]{1,8}", input in arb_shared_dict_children(), ) { let (n, children) = input; let staged_children = children.iter().map(|(suffix, values)| { let presence = presence(values); (suffix.clone(), values.clone(), presence) }); let staged = StagedProperty::SharedDict( StagedSharedDict::new(&struct_name, staged_children).expect("build shared dict"), ); let tile = encode_and_tile(vec![staged]); let expected_names: Vec = children .iter() .map(|(suffix, _)| format!("{struct_name}{suffix}")) .collect(); prop_assert_eq!(&tile.property_names, &expected_names); prop_assert_eq!(tile.features.len(), n); for (feat_idx, feat) in tile.features.iter().enumerate() { for (col_idx, (_, values)) in children.iter().enumerate() { prop_assert_eq!( &feat.properties[col_idx], &PropValue::Str(values[feat_idx].clone()) ); } } } } /// Build a [`TileLayer`] from heterogeneous column data (one `Vec` per column). fn tile_from_cols(cols: &[(&str, Vec)]) -> TileLayer { let n = cols.first().map_or(0, |(_, v)| v.len()); let property_names = cols.iter().map(|(name, _)| (*name).to_string()).collect(); let geom = geo_types::Geometry::::Point(Point::new(0, 0)); let features = (0..n) .map(|i| TileFeature { id: None, geometry: geom.clone(), properties: cols.iter().map(|(_, vals)| vals[i].clone()).collect(), }) .collect(); TileLayer { name: "test".to_string(), extent: 4096, property_names, features, } } fn tile_from_cols_with_ids(ids: &[Option], cols: &[(&str, Vec)]) -> TileLayer { let mut tile = tile_from_cols(cols); for (feature, id) in tile.features.iter_mut().zip(ids.iter().copied()) { feature.id = id; } tile } fn tile_from_ids(ids: &[Option]) -> TileLayer { let geom = geo_types::Geometry::::Point(Point::new(0, 0)); TileLayer { name: "test".to_string(), extent: 4096, property_names: vec![], features: ids .iter() .map(|&id| TileFeature { id, geometry: geom.clone(), properties: vec![], }) .collect(), } } /// Convert a `&[&str]` slice into a column of `PropValue::Str` values. fn str_vals(values: &[&str]) -> Vec { values .iter() .map(|s| PropValue::Str(Some((*s).to_string()))) .collect() } #[test] fn staging_uses_id_presence_analysis() { let all_present = tile_from_ids(&[Some(1), Some(2), Some(3)]); let analysis = all_present.analyze(false).unwrap(); let id = analysis.id.as_ref().expect("ID analysis"); assert!(id.stats.values_fit_u32()); let curve_params = all_present.curve_params(); let staged = StagedLayer::from_tile(all_present, Unsorted, &analysis, false, curve_params); assert!(matches!(staged.id, StagedId::U32(_))); let mixed = tile_from_ids(&[Some(1), None, Some(3)]); let analysis = mixed.analyze(false).unwrap(); let id = analysis.id.as_ref().expect("ID analysis"); assert!(id.stats.values_fit_u32()); let curve_params = mixed.curve_params(); let staged = StagedLayer::from_tile(mixed, Unsorted, &analysis, false, curve_params); assert!(matches!(staged.id, StagedId::OptU32(_))); let large = tile_from_ids(&[Some(u64::from(u32::MAX) + 1), None, Some(3)]); let analysis = large.analyze(false).unwrap(); let id = analysis.id.as_ref().expect("ID analysis"); assert!(!id.stats.values_fit_u32()); let curve_params = large.curve_params(); let staged = StagedLayer::from_tile(large, Unsorted, &analysis, false, curve_params); assert!(matches!(staged.id, StagedId::OptU64(_))); let all_null = tile_from_ids(&[None, None, None]); let analysis = all_null.analyze(false).unwrap(); assert_eq!(analysis.id, None); let curve_params = all_null.curve_params(); let staged = StagedLayer::from_tile(all_null, Unsorted, &analysis, false, curve_params); assert!(matches!(staged.id, StagedId::None)); } #[test] fn analyze_layer_classifies_id_and_property_presence() { let tile = tile_from_cols_with_ids( &[Some(1), None, Some(3)], &[ ( "all_present", [1u32, 2, 3] .iter() .map(|&v| PropValue::U32(Some(v))) .collect(), ), ( "mixed", vec![ PropValue::Bool(Some(true)), PropValue::Bool(None), PropValue::Bool(Some(false)), ], ), ( "all_null", vec![ PropValue::Str(None), PropValue::Str(None), PropValue::Str(None), ], ), ], ); let analysis = tile.analyze(true).unwrap(); let id = analysis.id.as_ref().expect("ID analysis"); assert_eq!(id.presence, Presence::SameAsProp(1)); assert_eq!(id.stats, PropertyTypedStats::Unsigned { min: 1, max: 3 }); assert_eq!(analysis.properties[0].presence, Presence::AllPresent); assert_eq!( analysis.properties[0].stats, PropertyTypedStats::Unsigned { min: 1, max: 3 } ); assert_eq!(analysis.properties[1].presence, Presence::Mixed); assert_eq!(analysis.properties[1].stats, PropertyTypedStats::Bool); assert_eq!(analysis.properties[2].presence, Presence::AllNull); assert_eq!(analysis.properties[2].stats, PropertyTypedStats::None); } #[test] fn analyze_layer_records_matching_property_presence_as_aliases() { let tile = tile_from_cols_with_ids( &[Some(1), None, Some(3), None], &[ ( "a", vec![ PropValue::U32(Some(1)), PropValue::U32(None), PropValue::U32(Some(3)), PropValue::U32(None), ], ), ( "b", vec![ PropValue::Bool(Some(true)), PropValue::Bool(None), PropValue::Bool(Some(false)), PropValue::Bool(None), ], ), ( "c", vec![ PropValue::Str(None), PropValue::Str(Some("x".to_string())), PropValue::Str(Some("y".to_string())), PropValue::Str(None), ], ), ( "d", vec![ PropValue::I32(Some(1)), PropValue::I32(Some(2)), PropValue::I32(Some(3)), PropValue::I32(Some(4)), ], ), ( "e", vec![ PropValue::F64(None), PropValue::F64(Some(1.0)), PropValue::F64(Some(2.0)), PropValue::F64(None), ], ), ( "f", vec![ PropValue::F32(None), PropValue::F32(None), PropValue::F32(None), PropValue::F32(None), ], ), ], ); let analysis = tile.analyze(false).unwrap(); assert_eq!( analysis.id.as_ref().expect("ID analysis").presence, Presence::SameAsProp(0) ); assert_eq!(analysis.properties[0].presence, Presence::Mixed); assert_eq!(analysis.properties[1].presence, Presence::SameAsProp(0)); assert_eq!(analysis.properties[2].presence, Presence::Mixed); assert_eq!(analysis.properties[3].presence, Presence::AllPresent); assert_eq!(analysis.properties[4].presence, Presence::SameAsProp(2)); assert_eq!(analysis.properties[5].presence, Presence::AllNull); } #[test] fn analyze_layer_tracks_typed_property_stats() { let tile = tile_from_cols(&[ ( "small_u64", vec![ PropValue::U64(Some(0)), PropValue::U64(Some(u64::from(u32::MAX))), ], ), ( "large_u64", vec![ PropValue::U64(Some(0)), PropValue::U64(Some(u64::from(u32::MAX) + 1)), ], ), ( "negative_i64", vec![PropValue::I64(Some(-1)), PropValue::I64(Some(2))], ), ( "names", vec![ PropValue::Str(Some("a".to_string())), PropValue::Str(Some("abcd".to_string())), ], ), ]); let analysis = tile.analyze(false).unwrap(); assert_eq!( analysis.properties[0].stats, PropertyTypedStats::Unsigned { min: 0, max: u64::from(u32::MAX) } ); assert!(analysis.properties[0].stats.values_fit_u32()); assert_eq!( analysis.properties[1].stats, PropertyTypedStats::Unsigned { min: 0, max: u64::from(u32::MAX) + 1 } ); assert!(!analysis.properties[1].stats.values_fit_u32()); assert_eq!( analysis.properties[2].stats, PropertyTypedStats::Signed { min: -1, max: 2 } ); assert!(!analysis.properties[2].stats.values_fit_u32()); assert_eq!( analysis.properties[3].stats, PropertyTypedStats::String { shared_dict: SharedDictRole::None } ); } #[rstest] #[case(vec![PropValue::I8(Some(1)), PropValue::I32(Some(2))])] #[case(vec![PropValue::I8(Some(1)), PropValue::I64(Some(2))])] #[case(vec![PropValue::I32(Some(1)), PropValue::I64(Some(2))])] #[case(vec![PropValue::U8(Some(1)), PropValue::U32(Some(2))])] #[case(vec![PropValue::U8(Some(1)), PropValue::U64(Some(2))])] #[case(vec![PropValue::U32(Some(1)), PropValue::U64(Some(2))])] #[case(vec![PropValue::I8(None), PropValue::I32(Some(2))])] #[case(vec![PropValue::I8(Some(1)), PropValue::I64(None)])] #[case(vec![PropValue::I32(None), PropValue::I64(Some(2))])] #[case(vec![PropValue::U8(None), PropValue::U32(Some(2))])] #[case(vec![PropValue::U8(Some(1)), PropValue::U64(None)])] #[case(vec![PropValue::U32(None), PropValue::U64(Some(2))])] #[case(vec![PropValue::F32(Some(1.0)), PropValue::F64(Some(2.0))])] #[case(vec![PropValue::F32(None), PropValue::F64(Some(2.0))])] #[case(vec![PropValue::F32(Some(1.0)), PropValue::F64(None)])] #[case(vec![PropValue::U32(None), PropValue::Str(Some("x".into()))])] fn analyze_layer_rejects_property_type_coercions(#[case] values: Vec) { let tile = tile_from_cols(&[("mixed", values)]); assert!(matches!( tile.analyze(false), Err(MltError::MixedPropertyTypes(0, property_name)) if property_name == "mixed" )); } #[rstest] #[case( vec![PropValue::Bool(None), PropValue::Bool(Some(true))], PropertyTypedStats::Bool )] #[case( vec![PropValue::I8(None), PropValue::I8(Some(-1))], PropertyTypedStats::Signed { min: -1, max: -1 } )] #[case( vec![PropValue::U8(None), PropValue::U8(Some(1))], PropertyTypedStats::Unsigned { min: 1, max: 1 } )] #[case( vec![PropValue::I32(None), PropValue::I32(Some(-2))], PropertyTypedStats::Signed { min: -2, max: -2 } )] #[case( vec![PropValue::U32(None), PropValue::U32(Some(2))], PropertyTypedStats::Unsigned { min: 2, max: 2 } )] #[case( vec![PropValue::I64(None), PropValue::I64(Some(-3))], PropertyTypedStats::Signed { min: -3, max: -3 } )] #[case( vec![PropValue::U64(None), PropValue::U64(Some(3))], PropertyTypedStats::Unsigned { min: 3, max: 3 } )] #[case( vec![PropValue::F32(None), PropValue::F32(Some(1.0))], PropertyTypedStats::F32 )] #[case( vec![PropValue::F64(None), PropValue::F64(Some(1.0))], PropertyTypedStats::F64 )] #[case( vec![PropValue::Str(None), PropValue::Str(Some("x".into()))], PropertyTypedStats::String { shared_dict: SharedDictRole::None, } )] fn analyze_layer_accepts_typed_nulls_matching_column_type( #[case] values: Vec, #[case] expected_stats: PropertyTypedStats, ) { let tile = tile_from_cols(&[("typed_null", values)]); let analysis = tile.analyze(false).unwrap(); assert_eq!(analysis.properties[0].presence, Presence::Mixed); assert_eq!(analysis.properties[0].stats, expected_stats); } #[test] fn staging_uses_presence_analysis_for_scalar_variants_and_skips_all_null() { let tile = tile_from_cols(&[ ( "all_present", [1u32, 2, 3] .iter() .map(|&v| PropValue::U32(Some(v))) .collect(), ), ( "mixed", vec![ PropValue::Bool(Some(true)), PropValue::Bool(None), PropValue::Bool(Some(false)), ], ), ( "all_null", vec![ PropValue::Str(None), PropValue::Str(None), PropValue::Str(None), ], ), ]); let staged = stage_tile(tile, Unsorted, false, false); assert_eq!(staged.properties.len(), 2); assert!(matches!(staged.properties[0], StagedProperty::U32(_))); assert!(matches!(staged.properties[1], StagedProperty::OptBool(_))); } #[test] fn analyze_layer_records_shared_dict_roles_by_property_index() { let vocab = &["Alice", "Bob", "Carol", "Dave"]; let tile = tile_from_cols(&[("name:en", str_vals(vocab)), ("name:de", str_vals(vocab))]); let analysis = tile.analyze(true).unwrap(); let SharedDictRole::Owner(prefix) = analysis.properties[0].stats.shared_dict() else { panic!("first string column should own the shared dictionary"); }; assert_eq!(prefix, "name:"); assert_eq!( analysis.properties[1].stats.shared_dict(), SharedDictRole::Member(0) ); } #[test] fn no_nulls_produces_encoded_output() { let props = vec![StagedProperty::u32("pop", vec![1, 2, 3])]; let mut enc = Encoder::default(); let mut codecs = Codecs::default(); write_properties(&props, &mut enc, &mut codecs).unwrap(); assert!( !enc.meta.is_empty(), "non-null column should write one column" ); } #[test] fn all_nulls_encodes_without_error() { let props = vec![StagedProperty::opt_i32("x", vec![None, None, None])]; let mut enc = Encoder::default(); let mut codecs = Codecs::default(); write_properties(&props, &mut enc, &mut codecs).unwrap(); } #[test] fn sequential_u32_encodes_successfully() { let props = vec![StagedProperty::u32("id", (0u32..1_000).collect())]; let mut enc = Encoder::default(); let mut codecs = Codecs::default(); write_properties(&props, &mut enc, &mut codecs).unwrap(); assert!(!enc.meta.is_empty()); } #[test] fn constant_u32_encodes_successfully() { let props = vec![StagedProperty::u32("val", vec![42u32; 500])]; let mut enc = Encoder::default(); let mut codecs = Codecs::default(); write_properties(&props, &mut enc, &mut codecs).unwrap(); assert!(!enc.meta.is_empty()); } #[test] fn similar_strings_grouped_into_shared_dict() { let vocab = &["Alice", "Bob", "Carol", "Dave"]; let tile = tile_from_cols(&[("name:en", str_vals(vocab)), ("name:de", str_vals(vocab))]); let res = tile.analyze(true).unwrap(); assert_eq!( res.properties[0].stats.shared_dict(), SharedDictRole::Owner("name:".to_string()) ); assert_eq!( res.properties[1].stats.shared_dict(), SharedDictRole::Member(0), "second similar string column should join the first column's SharedDict" ); } #[test] fn multiple_similar_string_columns_grouped() { let vocab = &["alpha", "beta", "gamma", "delta"]; let tile = tile_from_cols(&[ ("addr:zip", str_vals(vocab)), ("addr:street", str_vals(vocab)), ("addr:zipcode", str_vals(vocab)), ]); let res = tile.analyze(true).unwrap(); assert_eq!( res.properties[0].stats.shared_dict(), SharedDictRole::Owner("addr:".to_string()) ); assert_eq!( res.properties[1].stats.shared_dict(), SharedDictRole::Member(0) ); assert_eq!( res.properties[2].stats.shared_dict(), SharedDictRole::Member(0), "all similar string columns should join the first column's SharedDict" ); } #[test] fn dissimilar_strings_stay_scalar() { let tile = tile_from_cols(&[ ("city:de", str_vals(&["Munich", "Manheim", "Garching"])), ("city:colourado", str_vals(&["Black", "Red", "Gold"])), ]); let res = tile.analyze(true).unwrap(); assert_eq!(res.properties[0].stats.shared_dict(), SharedDictRole::None); assert_eq!(res.properties[1].stats.shared_dict(), SharedDictRole::None); } #[test] fn mixed_scalars_and_grouped_strings() { let vocab = &["alpha", "beta", "gamma"]; let tile = tile_from_cols(&[ ("id", (1u32..=3).map(|v| PropValue::U32(Some(v))).collect()), ("name:en", str_vals(vocab)), ("name:de", str_vals(vocab)), ( "count", [10i32, 20, 30] .iter() .map(|&v| PropValue::I32(Some(v))) .collect(), ), ]); let res = tile.analyze(true).unwrap(); assert_eq!(res.properties[0].stats.shared_dict(), SharedDictRole::None); assert_eq!( res.properties[1].stats.shared_dict(), SharedDictRole::Owner("name:".to_string()) ); assert_eq!( res.properties[2].stats.shared_dict(), SharedDictRole::Member(1) ); assert_eq!(res.properties[3].stats.shared_dict(), SharedDictRole::None); } #[test] fn encode_with_explicit_encoder_works() { let props = vec![StagedProperty::u32("id", (1_000u32..2_000).collect())]; let mut enc = Encoder::default(); let mut codecs = Codecs::default(); write_properties(&props, &mut enc, &mut codecs).unwrap(); assert!(!enc.meta.is_empty()); } ================================================ FILE: rust/mlt-core/src/encoder/sort.rs ================================================ //! Feature reordering for the optimizer use geo::CoordsIter as _; use geo_types::{Coord, Geometry}; use crate::codecs::hilbert::{hilbert_curve_params_from_bounds, hilbert_sort_key}; use crate::codecs::morton::morton_sort_key; use crate::decoder::TileLayer; use crate::encoder::model::CurveParams; /// Controls how features inside a layer are reordered before encoding. /// /// Reordering features changes their position in every parallel column /// (geometry, ID, and all properties simultaneously), so the caller must /// opt in explicitly. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, strum::EnumIter, strum::EnumCount)] pub enum SortStrategy { /// Preserve the original feature order — no reordering is applied. /// /// This is the default. #[default] Unsorted, /// Sort features by the Z-order (Morton) curve index of their first vertex. /// /// Fast to compute. Spatially close features end up adjacent in the /// stream, improving RLE run lengths for location-correlated properties /// and CPU cache locality during client-side decoding. /// SpatialMorton, /// Sort features by the Hilbert curve index of their first vertex. /// /// Slower to compute than Morton but achieves superior spatial locality. SpatialHilbert, /// Sort features by their feature ID in ascending order. Id, } impl TileLayer { /// Reorder features by `strategy`, using `params` as the curve normalization /// for [`SortStrategy::SpatialMorton`] / [`SortStrategy::SpatialHilbert`]. /// /// `params` is taken as a parameter (rather than recomputed here) so the /// same scan feeds the encoder's dictionary builders, see /// [`TileLayer::curve_params`]. /// /// [`SortStrategy::Unsorted`] is a no-op; layers with ≤1 feature are /// trivially unchanged. #[hotpath::measure] pub fn sort(&mut self, strategy: SortStrategy, params: CurveParams) { match strategy { SortStrategy::SpatialMorton | SortStrategy::SpatialHilbert => { let curve_key = if let SortStrategy::SpatialMorton = strategy { morton_sort_key } else { hilbert_sort_key }; self.features.sort_by_cached_key(|f| { first_vertex(&f.geometry).map_or(u64::MAX, |c| u64::from(curve_key(c, params))) }); } SortStrategy::Id => { self.features .sort_by_cached_key(|f| f.id.map_or(0, |v| v.saturating_add(1))); } SortStrategy::Unsorted => { // do nothing } } } /// Compute Hilbert/Morton [`CurveParams`] for this layer. /// /// The bounds are order-invariant, so the optimizer calls this once per /// layer and reuses the result across every sort trial and the encoder's /// dictionary builders. #[hotpath::measure] #[must_use] pub fn curve_params(&self) -> CurveParams { let (min_val, max_val) = self .features .iter() .flat_map(|f| f.geometry.coords_iter()) .fold((i32::MAX, i32::MIN), |(min, max), c| { (min.min(c.x).min(c.y), max.max(c.x).max(c.y)) }); hilbert_curve_params_from_bounds(min_val, max_val) } } /// Extract the coordinate of the first vertex of a geometry. fn first_vertex(geom: &Geometry) -> Option> { match geom { Geometry::::Point(p) => Some(p.0), Geometry::::Line(l) => Some(l.start), Geometry::::LineString(ls) => ls.0.first().copied(), Geometry::::Polygon(p) => p.exterior().0.first().copied(), Geometry::::MultiPoint(mp) => mp.0.first().map(|p| p.0), Geometry::::MultiLineString(mls) => mls.0.first().and_then(|ls| ls.0.first().copied()), Geometry::::MultiPolygon(mp) => { mp.0.first().and_then(|p| p.exterior().0.first().copied()) } Geometry::::Triangle(t) => Some(t.v1()), Geometry::::Rect(r) => Some(r.min()), Geometry::::GeometryCollection(gc) => gc.0.first().and_then(first_vertex), } } /// Return `true` if a spatial sort is likely to reduce compressed size. /// /// The heuristic: if the vertex bounding box spans more than /// `SPATIAL_HELP_COVERAGE` of the layer's tile extent on **both** axes, the /// features are too spread-out for locality clustering to help, so spatial /// sorting is skipped. pub(crate) fn spatial_sort_likely_to_help(layer: &TileLayer) -> bool { const SPATIAL_HELP_COVERAGE: f64 = 0.8; let extent = f64::from(layer.extent); if extent <= 0.0 || layer.features.is_empty() { return true; } let (min_x, max_x, min_y, max_y) = layer .features .iter() .filter_map(|f| first_vertex(&f.geometry)) .fold( (i32::MAX, i32::MIN, i32::MAX, i32::MIN), |(min_x, max_x, min_y, max_y), Coord:: { x, y }| { (min_x.min(x), max_x.max(x), min_y.min(y), max_y.max(y)) }, ); if min_x > max_x || min_y > max_y { return true; } let range_x = f64::from(max_x - min_x); let range_y = f64::from(max_y - min_y); let spread_x = range_x / extent; let spread_y = range_y / extent; !(spread_x > SPATIAL_HELP_COVERAGE && spread_y > SPATIAL_HELP_COVERAGE) } #[cfg(test)] mod tests { use geo_types::{Coord, Geometry as GeoGeom, Geometry, LineString, Point, Polygon}; use crate::decoder::{GeometryType, GeometryValues, RawGeometry, TileFeature, TileLayer}; use crate::encoder::{Codecs, Encoder, ExplicitEncoder, IntEncoder, SortStrategy, stage_tile}; use crate::test_helpers::{assert_empty, dec, into_layer01, parser}; use crate::{Layer, LazyParsed}; fn pt(x: i32, y: i32) -> Geometry { GeoGeom::Point(Point::new(x, y)) } fn ls(coords: &[(i32, i32)]) -> Geometry { GeoGeom::LineString(LineString::new( coords.iter().map(|&(x, y)| Coord { x, y }).collect(), )) } fn poly_square(x0: i32, y0: i32, side: i32) -> Geometry { let ring = LineString::new(vec![ Coord { x: x0, y: y0 }, Coord { x: x0 + side, y: y0, }, Coord { x: x0 + side, y: y0 + side, }, Coord { x: x0, y: y0 + side, }, Coord { x: x0, y: y0 }, ]); GeoGeom::Polygon(Polygon::new(ring, vec![])) } /// Encode + serialize + parse + decode a `GeometryValues` (round-trip). fn roundtrip_geom(decoded: &GeometryValues) -> GeometryValues { let mut enc = Encoder::default(); let mut codecs = Codecs::default(); decoded .clone() .write_to(&mut enc, &mut codecs) .expect("encode failed"); let buf = enc.data; let parsed = assert_empty(RawGeometry::from_bytes(&buf, &mut parser())); let mut d = dec(); let result = LazyParsed::Raw(parsed) .into_parsed(&mut d) .expect("decode failed"); assert!( d.consumed() > 0, "decoder should consume bytes after decode" ); result } /// Build the canonical (dense, wire-decoded) form of an ordered geometry sequence. fn canonical(geoms: &[Geometry]) -> GeometryValues { let mut decoded = GeometryValues::default(); for g in geoms { decoded.push_geom(g); } roundtrip_geom(&decoded) } /// Build a `TileLayer` from `geoms` and `ids`, apply `reorder_features`, /// and return it. fn layer_after_sort(geoms: &[Geometry], ids: &[u64], strategy: SortStrategy) -> TileLayer { let features: Vec = geoms .iter() .zip(ids.iter()) .map(|(g, &id)| TileFeature { id: Some(id), geometry: g.clone(), properties: vec![], }) .collect(); let mut layer = TileLayer { name: "test".to_string(), extent: 4096, property_names: vec![], features, }; let params = layer.curve_params(); layer.sort(strategy, params); layer } /// Sort, then encode+decode the result and compare to `canonical(expected)`. fn assert_sort_roundtrip( geoms: &[Geometry], ids: &[u64], strategy: SortStrategy, expected: &[Geometry], ) { let layer = layer_after_sort(geoms, ids, strategy); let mut sorted_decoded = GeometryValues::default(); for f in &layer.features { sorted_decoded.push_geom(&f.geometry); } let after_roundtrip = roundtrip_geom(&sorted_decoded); let expected_canonical = canonical(expected); assert_eq!( after_roundtrip, expected_canonical, "\nsorted geometry did not match expected after encode→decode round-trip\ \nvector_types after sort: {:?}\ \nvector_types expected: {:?}", sorted_decoded.vector_types, expected_canonical.vector_types, ); } // ── pure Points ────────────────────────────────────────────────────────── #[test] fn pure_points_id_sort_roundtrip() { assert_sort_roundtrip( &[pt(0, 0), pt(1, 1), pt(2, 2)], &[3, 2, 1], SortStrategy::Id, &[pt(2, 2), pt(1, 1), pt(0, 0)], ); } // ── pure LineStrings ───────────────────────────────────────────────────── #[test] fn pure_linestrings_id_sort_roundtrip() { assert_sort_roundtrip( &[ls(&[(0, 0), (0, 10)]), ls(&[(5, 5), (10, 10)])], &[2, 1], SortStrategy::Id, &[ls(&[(5, 5), (10, 10)]), ls(&[(0, 0), (0, 10)])], ); } // ── [Point, LineString, Point] ──────────────────────────────────────────── #[test] fn point_line_point_id_sort_to_line_point_point_roundtrip() { assert_sort_roundtrip( &[pt(0, 0), ls(&[(1, 0), (1, 5)]), pt(5, 5)], &[3, 1, 2], SortStrategy::Id, &[ls(&[(1, 0), (1, 5)]), pt(5, 5), pt(0, 0)], ); } #[test] fn point_line_point_id_sort_to_point_point_line_roundtrip() { assert_sort_roundtrip( &[pt(0, 0), ls(&[(1, 0), (1, 5)]), pt(5, 5)], &[1, 3, 2], SortStrategy::Id, &[pt(0, 0), pt(5, 5), ls(&[(1, 0), (1, 5)])], ); } // ── [Point, Polygon, Point] ─────────────────────────────────────────────── #[test] fn point_polygon_point_id_sort_roundtrip() { assert_sort_roundtrip( &[pt(0, 0), poly_square(10, 10, 5), pt(5, 5)], &[2, 1, 3], SortStrategy::Id, &[poly_square(10, 10, 5), pt(0, 0), pt(5, 5)], ); } // ── spatial Morton sort ─────────────────────────────────────────────────── #[test] fn point_line_point_morton_sort_roundtrip() { assert_sort_roundtrip( &[pt(2, 0), ls(&[(0, 0), (0, 5)]), pt(1, 0)], &[1, 2, 3], SortStrategy::SpatialMorton, &[ls(&[(0, 0), (0, 5)]), pt(1, 0), pt(2, 0)], ); } // ── already-sorted is identity ──────────────────────────────────────────── #[test] fn id_sort_already_sorted_is_identity_roundtrip() { let geoms = &[pt(0, 0), ls(&[(1, 0), (1, 5)]), pt(5, 5)]; assert_sort_roundtrip(geoms, &[1, 2, 3], SortStrategy::Id, geoms); } // ── ID column co-permuted with geometry ─────────────────────────────────── #[test] fn id_column_co_permuted_with_geometry() { let layer = layer_after_sort( &[pt(0, 0), ls(&[(1, 0), (1, 5)]), pt(5, 5)], &[3, 1, 2], SortStrategy::Id, ); let ids: Vec> = layer.features.iter().map(|f| f.id).collect(); assert_eq!(ids, vec![Some(1u64), Some(2), Some(3)]); // Verify geometry types match expected order let geom_types: Vec<&str> = layer .features .iter() .map(|f| GeometryType::try_from(&f.geometry).unwrap().into()) .collect(); assert_eq!(geom_types, vec!["LineString", "Point", "Point"]); } /// Build row-oriented tile layer from geometries and IDs (one feature per geometry). fn build_tile_layer(geoms: &[Geometry], ids: &[Option]) -> TileLayer { assert_eq!(geoms.len(), ids.len()); TileLayer { name: "test".to_string(), extent: 4096, property_names: vec![], features: geoms .iter() .zip(ids.iter()) .map(|(g, &id)| TileFeature { id, geometry: g.clone(), properties: vec![], }) .collect(), } } /// Encode the layer with a given sort strategy, decode it back, and return the `TileLayer`. /// This tests the full encode→decode roundtrip, verifying that sorting was applied. fn sort_encode_decode(tile: TileLayer, sort: SortStrategy) -> TileLayer { let enc_cfg = Encoder::default().cfg; let enc = Encoder::with_explicit(enc_cfg, ExplicitEncoder::for_id(IntEncoder::varint())); let mut codecs = Codecs::default(); let enc = stage_tile(tile, sort, false, enc_cfg.tessellate) .encode_into(enc, &mut codecs) .expect("encode failed"); // Serialize to bytes and reparse to get a `Layer01`. let buf = enc.into_layer_bytes().expect("into_layer_bytes failed"); let mut p = parser(); let layer_back = assert_empty(Layer::from_bytes(&buf, &mut p)); assert!(p.reserved() > 0, "parser should reserve bytes after parse"); let layer01 = into_layer01(layer_back); let mut d = dec(); let tile = layer01.into_tile(&mut d).expect("decode after sort failed"); assert!( d.consumed() > 0, "decoder should consume bytes after decode" ); tile } /// Rebuild a flat vertex buffer from the feature geometries in source order. fn vertices_from_source(source: &TileLayer) -> Vec { let mut geom = GeometryValues::default(); for f in &source.features { geom.push_geom(&f.geometry); } geom.vertices().unwrap_or_default().to_vec() } #[test] fn test_shared_morton_shift() { // P1 at (0, -10), P2 at (-10, 0). // With shared shift = 10: // P1 shifted: (10, 0) -> interleave(10, 0) = 68 // P2 shifted: (0, 10) -> interleave(0, 10) = 136 // P1 (key 68) < P2 (key 136), so expected order: [P1(0,-10), P2(-10,0)]. let tile = build_tile_layer(&[pt(0, -10), pt(-10, 0)], &[Some(1), Some(2)]); let source = sort_encode_decode(tile, SortStrategy::SpatialMorton); let verts = vertices_from_source(&source); assert_eq!(verts, vec![0, -10, -10, 0]); } #[test] fn test_id_sort_nulls_first() { let tile = build_tile_layer(&[pt(2, 2), pt(1, 1), pt(0, 0)], &[Some(10), None, Some(5)]); let source = sort_encode_decode(tile, SortStrategy::Id); let ids: Vec> = source.features.iter().map(|f| f.id).collect(); // Expected order: [None, Some(5), Some(10)] assert_eq!(ids, vec![None, Some(5), Some(10)]); let verts = vertices_from_source(&source); // Corresponding verts: [pt(1,1), pt(0,0), pt(2,2)] -> [1,1, 0,0, 2,2] assert_eq!(verts, vec![1, 1, 0, 0, 2, 2]); } #[test] fn test_mixed_geometry_morton_sort() { // [Point(2,0), LineString(0,0 -> 0,5), Point(1,0)] // Morton keys (assuming shift 0): // P1(2,0) -> 4 // LS(0,0) -> 0 // P2(1,0) -> 1 // Expected order: [LS, P2, P1] let tile = build_tile_layer( &[pt(2, 0), ls(&[(0, 0), (0, 5)]), pt(1, 0)], &[Some(1), Some(2), Some(3)], ); let source = sort_encode_decode(tile, SortStrategy::SpatialMorton); let types: Vec<_> = source .features .iter() .map(|f| GeometryType::try_from(&f.geometry).unwrap()) .collect(); assert_eq!( types, vec![ GeometryType::LineString, GeometryType::Point, GeometryType::Point ] ); let verts = vertices_from_source(&source); // Expected vertices: LS(0,0,0,5), P2(1,0), P1(2,0) assert_eq!(verts, vec![0, 0, 0, 5, 1, 0, 2, 0]); } } ================================================ FILE: rust/mlt-core/src/encoder/stream/codecs.rs ================================================ use std::collections::HashMap; use bytemuck::{NoUninit, cast_slice}; use fastpfor::FastPFor256; use num_traits::WrappingSub; use zigzag::ZigZag; use crate::codecs::bytes::encode_bools_to_bytes; use crate::codecs::rle::encode_byte_rle; use crate::decoder::{LogicalEncoding, PhysicalEncoding, RleMeta, StreamMeta, StreamType}; use crate::encoder; use crate::encoder::Encoder; use crate::encoder::model::StreamCtx; use crate::encoder::stream::logical::LogicalEncoder; use crate::encoder::stream::optimizer::DataProfile; use crate::encoder::write::{LogicalIntCodec, LogicalIntStreamKind, PhysicalIntStreamKind}; use crate::errors::MltResult; #[derive(Default)] pub struct Codecs { pub(crate) logical: LogicalCodecs, pub(crate) physical: PhysicalCodecs, } #[derive(Default)] pub struct PhysicalCodecs { pub(crate) u32_tmp: Vec, pub(crate) u8_tmp: Vec, pub(crate) fastpfor: FastPFor256, } #[derive(Default)] pub struct LogicalCodecs { pub(crate) u32_tmp: Vec, pub(crate) u32_tmp2: Vec, pub(crate) u64_tmp: Vec, pub(crate) u64_tmp2: Vec, u8_tmp: Vec, u8_tmp2: Vec, /// Reusable scratch for the Hilbert vertex-dictionary builder. The four /// slots are taken out via `mem::take` for the duration of a build so the /// caller can hold a `&[..]` view into one slot while passing `&mut Codecs` /// to a stream writer; capacity is preserved across geometry columns. pub(crate) hilbert_offsets: Vec, pub(crate) hilbert_indexed: Vec, pub(crate) hilbert_dict_xy: Vec, pub(crate) hilbert_remap: HashMap, } impl LogicalCodecs { #[hotpath::measure] pub(crate) fn encode_bools( &mut self, values: impl ExactSizeIterator, ) -> MltResult<(LogicalEncoding, &[u8])> { let num_values = u32::try_from(values.len())?; let data = encode_bools_to_bytes(values, &mut self.u8_tmp); let encoded = encode_byte_rle(data, &mut self.u8_tmp2); let meta = LogicalEncoding::Rle(RleMeta { runs: num_values.div_ceil(8), num_rle_values: u32::try_from(encoded.len())?, }); Ok((meta, encoded)) } } impl Codecs { pub(crate) fn write_bool_stream( &mut self, values: impl ExactSizeIterator, stream_type: StreamType, enc: &mut Encoder, ) -> MltResult<()> { let num_values = values.len(); let (logical, vals) = self.logical.encode_bools(values)?; let meta = StreamMeta::new2(stream_type, logical, PhysicalEncoding::None, num_values)?; encoder::write_stream_payload(&mut enc.data, meta, true, vals) } pub(crate) fn write_presence_stream( &mut self, values: impl ExactSizeIterator, enc: &mut Encoder, ) -> MltResult<()> { self.write_bool_stream(values, StreamType::Present, enc) } #[expect( clippy::unused_self, reason = "kept as a Codecs method to match the other stream writers" )] pub(crate) fn write_float_stream( &mut self, values: &[T], stream_type: StreamType, enc: &mut Encoder, ) -> MltResult<()> { #[cfg(not(target_endian = "little"))] compile_error!("not implemented for non-little-endian targets"); let meta = StreamMeta::new_none(stream_type, values.len())?; encoder::write_stream_payload(&mut enc.data, meta, false, cast_slice(values)) } pub(crate) fn write_int_stream( &mut self, values: &[T], ctx: &StreamCtx<'_>, enc: &mut Encoder, ) -> MltResult<()> where [T]: LogicalIntStreamKind, <<[T] as LogicalIntStreamKind>::Profile as ZigZag>::UInt: WrappingSub, LogicalCodecs: LogicalIntCodec<[T]>, { type Output = <[T] as LogicalIntStreamKind>::Output; use LogicalEncoding as LE; use PhysicalEncoding as PE; // FIXME: does StreamMeta encode values.len() or vals1.len()? if let Some(int_enc) = enc.override_int_enc(ctx) { let (le, vals) = match int_enc.logical { LogicalEncoder::None => (LE::None, self.logical.none(values)), LogicalEncoder::Delta => (LE::Delta, self.logical.delta(values)), LogicalEncoder::Rle => self.logical.rle(values)?, LogicalEncoder::DeltaRle => self.logical.delta_rle(values)?, }; let phys = int_enc.physical; return self .physical .write_encoded_as::>(ctx, enc, le, vals, phys); } if values.is_empty() { let vals1 = self.logical.none(values); let vals2 = Output::::none(&mut self.physical, vals1); let meta = StreamMeta::new2(ctx.stream_type, LE::None, PE::None, vals1.len())?; return encoder::write_stream_payload(&mut enc.data, meta, false, vals2); } let Self { logical, physical } = self; let mut alt = enc.try_alternatives(); let sample = logical.none(DataProfile::take_sample(values)); let profile = DataProfile::profile::<<[T] as LogicalIntStreamKind>::Profile>(sample); if profile.delta_is_beneficial() && (profile.rle_is_viable() || profile.delta_rle_is_viable()) { let (logical_enc, values) = logical.delta_rle(values)?; physical.write_alternatives::>( &mut alt, values, logical_enc, ctx.stream_type, )?; } if profile.delta_is_beneficial() { let values = logical.delta(values); physical.write_alternatives::>( &mut alt, values, LE::Delta, ctx.stream_type, )?; } if profile.rle_is_viable() { let (logical_enc, values) = logical.rle(values)?; physical.write_alternatives::>( &mut alt, values, logical_enc, ctx.stream_type, )?; } let values = logical.none(values); physical.write_alternatives::>(&mut alt, values, LE::None, ctx.stream_type) } } ================================================ FILE: rust/mlt-core/src/encoder/stream/encode_stream.rs ================================================ use std::collections::HashMap; use std::collections::hash_map::Entry; use crate::MltResult; #[cfg(any(test, feature = "__private"))] use crate::decoder::{DictionaryType, StreamMeta, StreamType}; #[cfg(any(test, feature = "__private"))] use crate::encoder::EncodedStream; use crate::errors::AsMltError as _; /// Deduplicate `values` preserving insertion order. /// Returns `(unique_strings, per_value_index)` where each entry in `per_value_index` is the /// index into `unique_strings` for the corresponding input value. pub(crate) fn dedup_strings>(values: &[S]) -> MltResult<(Vec<&str>, Vec)> { let mut unique: Vec<&str> = Vec::new(); let mut index: HashMap<&str, u32> = HashMap::new(); let mut indices = Vec::with_capacity(values.len()); for value in values { let s = value.as_ref(); let idx = match index.entry(s) { Entry::Occupied(e) => *e.get(), Entry::Vacant(e) => { let idx = u32::try_from(unique.len()).or_overflow()?; unique.push(s); *e.insert(idx) } }; indices.push(idx); } Ok((unique, indices)) } #[cfg(any(test, feature = "__private"))] impl EncodedStream { /// Encodes `f32`s into a stream #[hotpath::measure] pub fn encode_f32(values: &[f32]) -> MltResult { let data = values .iter() .flat_map(|f| f.to_le_bytes()) .collect::>(); let meta = StreamMeta::new_none(StreamType::Data(DictionaryType::None), values.len())?; Ok(Self { meta, data }) } /// Encodes `f64`s into a stream #[hotpath::measure] pub fn encode_f64(values: &[f64]) -> MltResult { let data = values .iter() .flat_map(|v| v.to_le_bytes()) .collect::>(); let meta = StreamMeta::new_none(StreamType::Data(DictionaryType::None), values.len())?; Ok(Self { meta, data }) } } ================================================ FILE: rust/mlt-core/src/encoder/stream/encoder.rs ================================================ use crate::encoder::stream::logical::LogicalEncoder; use crate::encoder::stream::physical::PhysicalEncoder; #[derive(Debug, Eq, PartialEq, Clone, Copy)] #[cfg_attr(test, derive(proptest_derive::Arbitrary))] #[cfg_attr(all(not(test), feature = "arbitrary"), derive(arbitrary::Arbitrary))] pub struct IntEncoder { pub(crate) logical: LogicalEncoder, pub(crate) physical: PhysicalEncoder, } impl IntEncoder { #[must_use] pub const fn new(logical: LogicalEncoder, physical: PhysicalEncoder) -> Self { Self { logical, physical } } #[must_use] pub fn delta_fastpfor() -> Self { Self::new(LogicalEncoder::Delta, PhysicalEncoder::FastPFOR) } #[must_use] pub fn delta_rle_fastpfor() -> Self { Self::new(LogicalEncoder::DeltaRle, PhysicalEncoder::FastPFOR) } #[must_use] pub fn delta_rle_varint() -> Self { Self::new(LogicalEncoder::DeltaRle, PhysicalEncoder::VarInt) } #[must_use] pub fn delta_varint() -> Self { Self::new(LogicalEncoder::Delta, PhysicalEncoder::VarInt) } #[must_use] pub fn fastpfor() -> Self { Self::new(LogicalEncoder::None, PhysicalEncoder::FastPFOR) } #[must_use] pub fn plain() -> Self { Self::new(LogicalEncoder::None, PhysicalEncoder::None) } #[must_use] pub fn rle_fastpfor() -> Self { Self::new(LogicalEncoder::Rle, PhysicalEncoder::FastPFOR) } #[must_use] pub fn rle_varint() -> Self { Self::new(LogicalEncoder::Rle, PhysicalEncoder::VarInt) } #[must_use] pub fn varint() -> Self { Self::new(LogicalEncoder::None, PhysicalEncoder::VarInt) } #[must_use] pub fn varint_with(logical: LogicalEncoder) -> Self { Self::new(logical, PhysicalEncoder::VarInt) } } ================================================ FILE: rust/mlt-core/src/encoder/stream/logical.rs ================================================ use std::fmt::Debug; use num_traits::PrimInt; use crate::MltResult; use crate::codecs::rle::encode_rle; use crate::decoder::RleMeta; /// RLE-encode `data` into `target` and return the matching `RleMeta`. /// /// `target` is treated as a scratch buffer: cleared before writing. /// `num_logical` is the expanded output length (stored in `RleMeta::num_rle_values`). pub(crate) fn apply_rle( data: &[T], num_logical: usize, target: &mut Vec, ) -> MltResult { let (runs_vec, vals_vec) = encode_rle(data); let meta = RleMeta { runs: u32::try_from(runs_vec.len())?, num_rle_values: u32::try_from(num_logical)?, }; target.clear(); target.extend_from_slice(&runs_vec); target.extend_from_slice(&vals_vec); Ok(meta) } #[derive(Debug, Eq, PartialEq, Clone, Copy, Default, strum::EnumIter)] #[cfg_attr(test, derive(proptest_derive::Arbitrary))] #[cfg_attr(all(not(test), feature = "arbitrary"), derive(arbitrary::Arbitrary))] pub enum LogicalEncoder { #[default] None, Delta, DeltaRle, Rle, // FIXME: add more of the LogicalEncoding strategies } #[cfg(test)] mod tests { use proptest::prelude::*; use super::*; use crate::decoder::RawStream; use crate::encoder::model::StreamCtx; use crate::encoder::{Codecs, Encoder, ExplicitEncoder, IntEncoder}; use crate::test_helpers::{assert_empty, dec, parser}; proptest! { #[test] fn test_u32_logical_roundtrip( values in prop::collection::vec(any::(), 0..100), logical in any::(), ) { let mut enc = Encoder::with_explicit( Encoder::default().cfg, ExplicitEncoder::all(IntEncoder::varint_with(logical)), ); let mut codecs = Codecs::default(); let ctx = StreamCtx::prop_data("test"); codecs.write_int_stream(&values, &ctx, &mut enc).unwrap(); let parsed = assert_empty(RawStream::from_bytes(&enc.data, &mut parser())); let decoded = parsed.decode_u32s(&mut dec()).unwrap(); prop_assert_eq!(decoded, values); } #[test] fn test_i32_logical_roundtrip( values in prop::collection::vec(any::(), 0..100), logical in any::(), ) { let mut enc = Encoder::with_explicit( Encoder::default().cfg, ExplicitEncoder::all(IntEncoder::varint_with(logical)), ); let mut codecs = Codecs::default(); let ctx = StreamCtx::prop_data("test"); codecs.write_int_stream(&values, &ctx, &mut enc).unwrap(); let parsed = assert_empty(RawStream::from_bytes(&enc.data, &mut parser())); let decoded = parsed.decode_i32s(&mut dec()).unwrap(); prop_assert_eq!(decoded, values); } #[test] fn test_u64_logical_roundtrip( values in prop::collection::vec(any::(), 0..100), logical in any::(), ) { let mut enc = Encoder::with_explicit( Encoder::default().cfg, ExplicitEncoder::all(IntEncoder::varint_with(logical)), ); let mut codecs = Codecs::default(); let ctx = StreamCtx::prop_data("test"); codecs.write_int_stream(&values, &ctx, &mut enc).unwrap(); let parsed = assert_empty(RawStream::from_bytes(&enc.data, &mut parser())); let decoded = parsed.decode_u64s(&mut dec()).unwrap(); prop_assert_eq!(decoded, values); } #[test] fn test_i64_logical_roundtrip( values in prop::collection::vec(any::(), 0..100), logical in any::(), ) { let mut enc = Encoder::with_explicit( Encoder::default().cfg, ExplicitEncoder::all(IntEncoder::varint_with(logical)), ); let mut codecs = Codecs::default(); let ctx = StreamCtx::prop_data("test"); codecs.write_int_stream(&values, &ctx, &mut enc).unwrap(); let parsed = assert_empty(RawStream::from_bytes(&enc.data, &mut parser())); let decoded = parsed.decode_i64s(&mut dec()).unwrap(); prop_assert_eq!(decoded, values); } } } ================================================ FILE: rust/mlt-core/src/encoder/stream/mod.rs ================================================ mod codecs; mod encode_stream; pub(crate) use encode_stream::dedup_strings; mod encoder; pub(crate) mod logical; mod model; mod optimizer; mod physical; #[cfg(test)] mod tests; pub(crate) mod write; #[cfg(feature = "__private")] pub use codecs::{Codecs, PhysicalCodecs}; #[cfg(not(feature = "__private"))] pub(crate) use codecs::{Codecs, PhysicalCodecs}; pub use encoder::IntEncoder; #[cfg(feature = "__private")] pub use logical::LogicalEncoder; #[cfg(all(test, not(feature = "__private")))] pub(crate) use logical::LogicalEncoder; #[cfg(any(test, feature = "__private"))] pub use model::*; #[cfg(feature = "__private")] pub use physical::PhysicalEncoder; #[cfg(all(test, not(feature = "__private")))] pub(crate) use physical::PhysicalEncoder; pub(crate) use write::write_stream_payload; ================================================ FILE: rust/mlt-core/src/encoder/stream/model.rs ================================================ #[cfg(any(test, feature = "__private"))] use std::io::Write; #[cfg(any(test, feature = "__private"))] use crate::decoder::StreamMeta; /// Owned variant of [`RawStream`](crate::decoder::RawStream). #[cfg(any(test, feature = "__private"))] #[derive(Debug, Clone, PartialEq)] pub struct EncodedStream { pub meta: StreamMeta, pub(crate) data: Vec, } #[cfg(any(test, feature = "__private"))] impl EncodedStream { pub fn write_to(&self, writer: &mut W) -> std::io::Result<()> { writer.write_all(&self.data) } } ================================================ FILE: rust/mlt-core/src/encoder/stream/optimizer.rs ================================================ use num_traits::{AsPrimitive as _, PrimInt as _, WrappingSub, Zero as _}; use zigzag::ZigZag; /// Minimum number of values to profile / compete on. /// /// Below this threshold the full slice is used regardless of its length. const MIN_SAMPLE: usize = 512; /// Hard upper bound on competition sample size. const MAX_SAMPLE: usize = 16_384; /// RLE is only worthwhile when runs are on average at least this long. const RLE_MIN_AVG_RUN_LENGTH: f64 = 2.0; /// Sampling-based encoder selection #[derive(Debug, Clone, Default)] pub struct DataProfile { /// Average run length in the sample. /// /// A run is a maximal sequence of identical consecutive values. /// `avg_run_length = sample_len / num_runs`. avg_run_length: f64, /// Average run length of the zigzag-encoded delta stream. /// /// This is computed over the deltas between consecutive sample values, /// excluding the initial/base value, so the effective stream length is /// `sample_len - 1`. /// /// For sequential data like `[1, 2, 3, ...]` the raw values are all /// distinct (`avg_run_length == 1`) but the delta stream is constant, so /// `delta_avg_run_length == N - 1`, making `DeltaRle` extremely effective. delta_avg_run_length: f64, /// `true` if the sample values are sorted in ascending or descending order. is_sorted: bool, /// Sum of per-value bit widths across the sample. /// /// Aggregate bit count captures the *typical* benefit of delta encoding, /// unlike max bit width which can be pinned by a single outlier. total_bits: u32, /// Sum of per-value bit widths of the zigzag-delta stream. delta_total_bits: u32, } impl DataProfile { /// Profile a `u32` sample in a single pass. #[must_use] #[expect(clippy::cast_precision_loss)] pub(crate) fn profile(sample: &[T::UInt]) -> Self where T: ZigZag, ::UInt: WrappingSub, { if sample.is_empty() { return Self::default(); } let type_bits = T::zero().leading_zeros(); let mut runs: usize = 1; let mut delta_runs: usize = 1; let mut is_sorted_rising = true; let mut is_sorted_falling = true; let mut total_bits: u32 = type_bits - sample[0].leading_zeros(); let mut delta_total_bits: u32 = 0; let mut prev = sample[0]; let mut prev_zz: T::UInt = T::UInt::zero(); for (i, &v) in sample[1..].iter().enumerate() { if v != prev { runs += 1; } if v < prev { is_sorted_rising = false; } else if prev < v { is_sorted_falling = false; } let delta_bits: T::UInt = v.wrapping_sub(&prev); let delta_signed: T = delta_bits.as_(); let zz = T::encode(delta_signed); if i == 0 { prev_zz = zz; } else if zz != prev_zz { delta_runs += 1; prev_zz = zz; } total_bits += type_bits - v.leading_zeros(); delta_total_bits += type_bits - zz.leading_zeros(); prev = v; } let delta_len = sample.len().saturating_sub(1).max(1); Self { avg_run_length: sample.len() as f64 / runs as f64, delta_avg_run_length: delta_len as f64 / delta_runs as f64, is_sorted: is_sorted_rising || is_sorted_falling, total_bits, delta_total_bits, } } pub(crate) fn take_sample(values: &[T]) -> &[T] { let len = (values.len() / 100).clamp(MIN_SAMPLE, MAX_SAMPLE); if values.len() <= len { values } else { let start = (values.len() / 2).saturating_sub(len / 2); &values[start..start + len] } } /// Returns `true` if RLE is a sensible candidate based on this profile. /// /// An average run length above the threshold means values repeat frequently /// enough that the run-length and unique-value arrays will be compact. #[must_use] pub(crate) fn rle_is_viable(&self) -> bool { self.avg_run_length >= RLE_MIN_AVG_RUN_LENGTH } /// Returns `true` if RLE on the delta-transformed stream is viable. /// /// For sequential or constant-delta data the raw values are all distinct /// but the zigzag-delta values are identical, making `DeltaRle` optimal. #[must_use] pub(crate) fn delta_rle_is_viable(&self) -> bool { self.delta_avg_run_length >= RLE_MIN_AVG_RUN_LENGTH } /// Returns `true` if Delta encoding is expected to be beneficial. #[must_use] pub(crate) fn delta_is_beneficial(&self) -> bool { self.is_sorted || self.delta_total_bits < self.total_bits } } #[cfg(test)] mod tests { use super::*; fn assert_profile_flags(profile: &DataProfile, delta: bool, rle: bool, delta_rle: bool) { assert_eq!(profile.delta_is_beneficial(), delta, "delta"); assert_eq!(profile.rle_is_viable(), rle, "rle"); assert_eq!(profile.delta_rle_is_viable(), delta_rle, "delta_rle"); } #[test] fn profile_sequential_u32_uses_delta_rle_without_raw_rle() { // All-distinct stream -> avg_run_length == 1 -> no raw-RLE candidate. // But sequential data has constant deltas -> delta-RLE is viable. let data: Vec = (0..100).collect(); let profile = DataProfile::profile::(DataProfile::take_sample(&data)); assert!(profile.is_sorted); assert_profile_flags(&profile, true, false, true); } #[test] fn profile_constant_u32_uses_raw_and_delta_rle() { let data = vec![1234u32; 500]; let profile = DataProfile::profile::(DataProfile::take_sample(&data)); assert!(profile.is_sorted); assert_profile_flags(&profile, true, true, true); } #[test] fn profile_sequential_u64_uses_delta_rle_without_raw_rle() { let data: Vec = (0u64..500).collect(); let profile = DataProfile::profile::(DataProfile::take_sample(&data)); assert!(profile.is_sorted); assert_profile_flags(&profile, true, false, true); } #[test] fn profile_empty_has_no_optimizing_flags() { let values: &[u32] = &[]; let profile = DataProfile::profile::(DataProfile::take_sample(values)); assert!(!profile.is_sorted); assert_profile_flags(&profile, false, false, false); } } ================================================ FILE: rust/mlt-core/src/encoder/stream/physical.rs ================================================ use crate::decoder::PhysicalEncoding; use crate::{MltError, MltResult}; impl PhysicalEncoding { pub fn parse(value: u8) -> MltResult { Self::try_from(value).or(Err(MltError::ParsingPhysicalEncoding(value))) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)] #[cfg_attr(test, derive(proptest_derive::Arbitrary))] #[cfg_attr(all(not(test), feature = "arbitrary"), derive(arbitrary::Arbitrary))] pub enum PhysicalEncoder { None, /// Can produce better results in combination with a heavyweight compression scheme like `Gzip`. /// Simple compression scheme where the encoding is easier to implement compared to `FastPFOR`. VarInt, /// Preferred, tends to produce the best compression ratio and decoding performance. /// /// Does not support u64/i64 integers FastPFOR, } ================================================ FILE: rust/mlt-core/src/encoder/stream/tests.rs ================================================ use proptest::prelude::*; use rstest::rstest; use crate::MltError; use crate::decoder::{ DictionaryType, IntEncoding, LengthType, LogicalEncoding, LogicalValue, Morton, OffsetType, PhysicalEncoding, RawStream, RleMeta, StreamMeta, StreamType, }; use crate::encoder::model::StreamCtx; use crate::encoder::{ Codecs, EncodedStream, Encoder, ExplicitEncoder, IntEncoder, PhysicalEncoder, }; use crate::test_helpers::{assert_empty, dec, parser}; use crate::utils::BinarySerializer as _; fn roundtrip_stream<'a>(buffer: &'a mut Vec, stream: &EncodedStream) -> RawStream<'a> { buffer.clear(); buffer.write_stream(stream).unwrap(); assert_empty(RawStream::from_bytes(buffer, &mut parser())) } fn roundtrip_stream_u32s(wire: &[u8]) -> Vec { let parsed_stream = assert_empty(RawStream::from_bytes(wire, &mut parser())); let mut decoder = dec(); let values = parsed_stream.decode_u32s(&mut decoder).unwrap(); if !values.is_empty() { assert!( decoder.consumed() > 0, "decoder should consume bytes after decode" ); } values } fn make_logical_val(logical_encoding: LogicalEncoding, num_values: usize) -> LogicalValue { LogicalValue::new( StreamMeta::new2( StreamType::Data(DictionaryType::None), logical_encoding, PhysicalEncoding::VarInt, num_values, ) .unwrap(), ) } /// Test case for stream decoding tests #[derive(Debug)] struct StreamTestCase { meta: StreamMeta, data: &'static [u8], /// Expected contents of the physical decode buffer after `decode_bits_u32`. expected_u32_logical_value: Option>, } /// Generator function that creates a set of test cases for stream decoding fn generate_stream_test_cases() -> Vec { vec![ // Basic VarInt test case StreamTestCase { meta: StreamMeta::new( StreamType::Data(DictionaryType::None), IntEncoding::new(LogicalEncoding::None, PhysicalEncoding::VarInt), 4, ), data: &[0x04, 0x03, 0x02, 0x01], expected_u32_logical_value: Some(vec![4, 3, 2, 1]), }, // Basic Encoded test case StreamTestCase { meta: StreamMeta::new( StreamType::Data(DictionaryType::None), IntEncoding::none(), 1, ), data: &[0x04, 0x03, 0x02, 0x01], expected_u32_logical_value: Some(vec![0x0102_0304]), }, ] } fn create_stream_from_test_case(test_case: &StreamTestCase) -> RawStream<'_> { RawStream::new(test_case.meta, test_case.data) } #[test] fn test_decode_bits_u32() { let test_cases = generate_stream_test_cases(); for test_case in test_cases { if let Some(expected_buf) = &test_case.expected_u32_logical_value { let stream = create_stream_from_test_case(&test_case); let mut buf = Vec::new(); stream .decode_bits_u32(&mut buf, &mut dec()) .expect("Should successfully decode u32 values"); assert_eq!( &buf, expected_buf, "Should produce decoded u32 values correctly" ); } } } #[rstest] // ZigZag pairs: [(0,0),(2,4),(2,4)] -> [(0,0),(1,2),(1,2)] // Delta: [(0,0),(1,2),(1,2)] -> [(0,0),(1,2),(2,4)] #[case::componentwise_delta(LogicalEncoding::ComponentwiseDelta, vec![0u32, 0, 2, 4, 2, 4], vec![0i32, 0, 1, 2, 2, 4] )] // ZigZag: [0,1,2,1,2] -> [0,-1,1,-1,1] // Delta: [0,-1,1,-1,1] -> [0,-1,0,-1,0] #[case::delta(LogicalEncoding::Delta, vec![0u32, 1, 2, 1, 2], vec![0i32, -1, 0, -1, 0])] // RLE: [3,2] [0,2] -> [0,0,0,2,2] // ZigZag: [0,0,0,2,2] -> [0,0,0,1,1] // Delta: [0,0,0,1,1] -> [0,0,0,1,2] #[case::delta_rle(LogicalEncoding::DeltaRle(RleMeta { runs: 2, num_rle_values: 5 }), vec![3u32, 2, 0, 2], vec![0i32, 0, 0, 1, 2] )] #[case::delta_empty(LogicalEncoding::Delta, vec![], vec![])] fn test_decode_i32( #[case] logical_encoding: LogicalEncoding, #[case] input_data: Vec, #[case] expected: Vec, ) { let result = make_logical_val(logical_encoding, input_data.len()).decode_i32(&input_data, &mut dec()); assert!(result.is_ok(), "should decode successfully"); assert_eq!(result.unwrap(), expected, "should match expected output"); } #[rstest] #[case::empty(LogicalEncoding::None, vec![], vec![])] #[case::new_encoded(LogicalEncoding::None, vec![10u32, 20, 30, 40], vec![10u32, 20, 30, 40])] #[case::rle(LogicalEncoding::Rle(RleMeta { runs: 3, num_rle_values: 6 }), vec![3u32, 2, 1, 10, 20, 30], vec![10u32, 10, 10, 20, 20, 30] )] // ZigZag: [0,2,2,2,2] -> [0,1,1,1,1] // Delta: [0,1,1,1,1] -> [0,1,2,3,4] #[case::delta(LogicalEncoding::Delta, vec![0u32, 2, 2, 2, 2], vec![0u32, 1, 2, 3, 4])] fn test_decode_u32( #[case] logical_encoding: LogicalEncoding, #[case] input_data: Vec, #[case] expected: Vec, ) { let result = make_logical_val(logical_encoding, input_data.len()).decode_u32(&input_data, &mut dec()); assert!(result.is_ok(), "should decode successfully"); assert_eq!(result.unwrap(), expected, "should match expected output"); } #[rstest] #[case::basic(vec![1, 2, 3, 4, 5, 100, 1000])] #[case::large(vec![1_000_000; 256])] #[case::edge_values(vec![0, 1, 2, 4, 8, 16, 1024, 65535, 1_000_000_000, u32::MAX])] #[case::empty(vec![])] fn test_fastpfor_roundtrip(#[case] values: Vec) { let mut enc = Encoder::with_explicit( Encoder::default().cfg, ExplicitEncoder::all(IntEncoder::fastpfor()), ); let codecs = &mut Codecs::default(); let ctx = StreamCtx::prop_data("test"); codecs.write_int_stream(&values, &ctx, &mut enc).unwrap(); let decoded_values = roundtrip_stream_u32s(&enc.data); assert_eq!(decoded_values, values); } /// Test roundtrip: write -> parse -> equality for stream serialization #[rstest] #[case::new_encoded(StreamType::Data(DictionaryType::None), 2, LogicalEncoding::None, PhysicalEncoding::None, vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08], false )] #[case::new_encoded(StreamType::Data(DictionaryType::None), 2, LogicalEncoding::ComponentwiseDelta, PhysicalEncoding::None, vec![0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00], false )] #[case::new_encoded(StreamType::Offset(OffsetType::Vertex), 3, LogicalEncoding::None, PhysicalEncoding::None, vec![0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00], false )] #[case::varint(StreamType::Data(DictionaryType::None), 4, LogicalEncoding::None, PhysicalEncoding::VarInt, vec![0x0A, 0x14, 0x1E, 0x28], false )] #[case::varint(StreamType::Data(DictionaryType::None), 5, LogicalEncoding::Delta, PhysicalEncoding::VarInt, vec![0x00, 0x02, 0x02, 0x02, 0x02], false )] #[case::varint(StreamType::Data(DictionaryType::None), 3, LogicalEncoding::PseudoDecimal, PhysicalEncoding::VarInt, vec![0x01, 0x02, 0x03], false )] #[case::varint(StreamType::Length(LengthType::VarBinary), 3, LogicalEncoding::Delta, PhysicalEncoding::VarInt, vec![0x00, 0x02, 0x02], false )] #[case::rle(StreamType::Data(DictionaryType::None), 6, LogicalEncoding::Rle(RleMeta { runs: 3, num_rle_values: 6 }), PhysicalEncoding::VarInt, vec![0x03, 0x02, 0x01, 0x0A, 0x14, 0x1E], false )] #[case::rle(StreamType::Data(DictionaryType::None), 5, LogicalEncoding::DeltaRle(RleMeta { runs: 2, num_rle_values: 5 }), PhysicalEncoding::VarInt, vec![0x03, 0x02, 0x00, 0x02], false )] #[case::morton(StreamType::Data(DictionaryType::Morton), 4, LogicalEncoding::Morton(Morton { bits: 16, shift: 0 }), PhysicalEncoding::VarInt, vec![0x01, 0x02, 0x03, 0x04], false )] #[case::boolean(StreamType::Present, 16, LogicalEncoding::Rle(RleMeta { runs: 2, num_rle_values: 2 }), PhysicalEncoding::VarInt, vec![0xFF, 0x00], true )] fn test_stream_roundtrip( #[case] stream_type: StreamType, #[case] num_values: u32, #[case] logical_encoding: LogicalEncoding, #[case] physical_encoding: PhysicalEncoding, #[case] data_bytes: Vec, #[case] is_bool: bool, ) { let stream = EncodedStream { meta: StreamMeta::new( stream_type, IntEncoding::new(logical_encoding, physical_encoding), num_values, ), data: data_bytes, }; // Write to buffer let mut buffer = Vec::new(); if is_bool { buffer.write_boolean_stream(&stream).unwrap(); } else { buffer.write_stream(&stream).unwrap(); } // Parse back let parsed = assert_empty(if is_bool { RawStream::parse_bool(&buffer, &mut parser()) } else { RawStream::from_bytes(&buffer, &mut parser()) }); assert_eq!(parsed.meta, stream.meta, "metadata mismatch"); assert_eq!(stream.data.as_slice(), parsed.data, "data mismatch"); } #[test] fn test_morton_parse_rejects_too_many_bits() { let stream = EncodedStream { meta: StreamMeta::new( StreamType::Data(DictionaryType::Morton), IntEncoding::new( LogicalEncoding::Morton(Morton { bits: 17, shift: 0 }), PhysicalEncoding::VarInt, ), 1, ), data: vec![0], }; let mut buffer = Vec::new(); buffer.write_stream(&stream).unwrap(); let err = RawStream::from_bytes(&buffer, &mut parser()).unwrap_err(); assert!(matches!(err, MltError::InvalidMortonBits(17))); } /// OOM regression: `VarInt` stream with huge `num_values` but `byte_length=0`. /// /// `Wire: stream_type=0x00 | enc=0x02(VarInt) | num_values=0xd5_ff_d5_ff_03 | byte_length=0x00` /// Before the budget fix, `parse_varint_vec` called `Vec::with_capacity(1_073_053_653)` → ~4 GB OOM. /// Now the memory budget is checked at parse time: `num_values * 8 = ~8 GB > 10 MB limit`. #[test] fn test_varint_stream_huge_num_values_empty_data() { // enc_byte = 0x02 → logical1=0(None), logical2=0(None), physical=2(VarInt) // num_values = 0xd5 0xff 0xd5 0xff 0x03 = 1_073_053_653 (valid u32, 5-byte varint) // byte_length = 0x00 → 0 bytes of data let wire: &[u8] = &[0x00, 0x02, 0xd5, 0xff, 0xd5, 0xff, 0x03, 0x00]; // Parsing must fail: budget reserves num_values * 8 ≈ 8 GB which exceeds the 10 MB limit. let result = RawStream::from_bytes(wire, &mut parser()); assert!( result.is_err(), "parse must fail when num_values * 8 exceeds the memory budget" ); } /// RLE mismatch regression: `num_rle_values` in stream header doesn't equal sum of runs. /// /// `RleMeta::decode` must return an error instead of allocating based on the /// header-declared `num_rle_values` when the actual run sum differs. #[test] fn test_rle_num_rle_values_mismatch() { // runs=1, num_rle_values=u32::MAX (declared), but the single run has value 1. // Sum of runs = 1 ≠ u32::MAX → must error before allocating ~16 GB. let rle = RleMeta { runs: 1, num_rle_values: u32::MAX, }; // data = [run_len=1, value=42] (1 run of length 1 with value 42) let data = [1u32, 42u32]; let result = rle.decode::(&data, &mut dec()); assert!( result.is_err(), "must reject mismatched num_rle_values before allocating" ); } fn encoding_no_fastpfor() -> impl Strategy { any::().prop_filter("not fastpfor", |v| v.physical != PhysicalEncoder::FastPFOR) } /// Deduplicate strings and return (`offset_indices`, `unique_lengths`). fn dedup_and_get_parts(values: &[&str]) -> (Vec, Vec) { use crate::encoder::stream::dedup_strings; use crate::utils::strings_to_lengths; let (unique, offset_indices) = dedup_strings(values).unwrap(); let lengths = strings_to_lengths(&unique).unwrap(); (offset_indices, lengths) } #[rstest] #[case::with_duplicates(&["apple", "banana", "apple", "cherry", "banana", "apple"], &[0, 1, 0, 2, 1, 0], &[5, 6, 6] )] #[case::all_unique(&["a", "b", "c", "d"], &[0, 1, 2, 3], &[1, 1, 1, 1])] #[case::all_same(&["same", "same", "same", "same"], &[0, 0, 0, 0], &[4])] fn test_encode_strings_dict( #[case] values: &[&str], #[case] expected_offsets: &[u32], #[case] expected_lengths: &[u32], ) { let (offsets, lengths) = dedup_and_get_parts(values); assert_eq!(offsets, expected_offsets); assert_eq!(lengths, expected_lengths); } proptest! { #[test] fn test_i8_roundtrip( values in prop::collection::vec(any::(), 0..100), encoding in any::(), ) { let widened: Vec = values.iter().map(|&v| i32::from(v)).collect(); let mut enc = Encoder::with_explicit(Encoder::default().cfg, ExplicitEncoder::all(encoding)); let mut codecs = Codecs::default(); codecs.write_int_stream(&widened, &StreamCtx::prop_data("test"), &mut enc).unwrap(); let parsed_stream = assert_empty(RawStream::from_bytes(&enc.data, &mut parser())); let decoded_values = parsed_stream.decode_i8s(&mut dec()).unwrap(); assert_eq!(decoded_values, values); } #[test] fn test_u8_roundtrip( values in prop::collection::vec(any::(), 0..100), encoding in any::() ) { let widened: Vec = values.iter().map(|&v| u32::from(v)).collect(); let mut enc = Encoder::with_explicit(Encoder::default().cfg, ExplicitEncoder::all(encoding)); let mut codecs = Codecs::default(); codecs.write_int_stream(&widened, &StreamCtx::prop_data("test"), &mut enc).unwrap(); let parsed_stream = assert_empty(RawStream::from_bytes(&enc.data, &mut parser())); let decoded_values = parsed_stream.decode_u8s(&mut dec()).unwrap(); assert_eq!(decoded_values, values); } #[test] fn test_u32_roundtrip( values in prop::collection::vec(any::(), 0..100), encoding in any::() ) { let mut enc = Encoder::with_explicit(Encoder::default().cfg, ExplicitEncoder::all(encoding)); let mut codecs = Codecs::default(); codecs.write_int_stream(&values, &StreamCtx::prop_data("test"), &mut enc).unwrap(); let decoded_values = roundtrip_stream_u32s(&enc.data); assert_eq!(decoded_values, values); } #[test] fn test_i32_roundtrip( values in prop::collection::vec(any::(), 0..100), encoding in any::(), ) { let mut enc = Encoder::with_explicit(Encoder::default().cfg, ExplicitEncoder::all(encoding)); let mut codecs = Codecs::default(); codecs.write_int_stream(&values, &StreamCtx::prop_data("test"), &mut enc).unwrap(); let parsed_stream = assert_empty(RawStream::from_bytes(&enc.data, &mut parser())); let decoded_values = parsed_stream.decode_i32s(&mut dec()).unwrap(); assert_eq!(decoded_values, values); } #[test] fn test_u64_roundtrip( values in prop::collection::vec(any::(), 0..100), encoding in encoding_no_fastpfor() ) { let mut enc = Encoder::with_explicit(Encoder::default().cfg, ExplicitEncoder::all(encoding)); let mut codecs = Codecs::default(); codecs.write_int_stream(&values, &StreamCtx::prop_data("test"), &mut enc).unwrap(); let parsed_stream = assert_empty(RawStream::from_bytes(&enc.data, &mut parser())); let decoded_values = parsed_stream.decode_u64s(&mut dec()).unwrap(); assert_eq!(decoded_values, values); } #[test] fn test_i64_roundtrip( values in prop::collection::vec(any::(), 0..100), encoding in encoding_no_fastpfor() ) { let mut enc = Encoder::with_explicit(Encoder::default().cfg, ExplicitEncoder::all(encoding)); let mut codecs = Codecs::default(); codecs.write_int_stream(&values, &StreamCtx::prop_data("test"), &mut enc).unwrap(); let parsed_stream = assert_empty(RawStream::from_bytes(&enc.data, &mut parser())); let decoded_values = parsed_stream.decode_i64s(&mut dec()).unwrap(); assert_eq!(decoded_values, values); } #[test] fn test_f32_roundtrip(values in prop::collection::vec(any::(), 0..100)) { let owned_stream = EncodedStream::encode_f32(&values).unwrap(); let mut buf = Vec::new(); let parsed_stream = roundtrip_stream(&mut buf, &owned_stream); let decoded_values = parsed_stream.decode_f32s(&mut dec()).unwrap(); assert_eq!(decoded_values.len(), values.len()); for (v1, v2) in decoded_values.iter().zip(values.iter()) { assert_eq!( v1.to_bits(), v2.to_bits(), "despite being semantically equal, the values are not actually equal" ); } } #[test] fn test_f64_roundtrip(values in prop::collection::vec(any::(), 0..100)) { let owned_stream = EncodedStream::encode_f64(&values).unwrap(); let mut buf = Vec::new(); let parsed_stream = roundtrip_stream(&mut buf, &owned_stream); let decoded_values = parsed_stream.decode_f64s(&mut dec()).unwrap(); assert_eq!(decoded_values.len(), values.len()); for (v1, v2) in decoded_values.iter().zip(values.iter()) { assert_eq!( v1.to_bits(), v2.to_bits(), "despite being semantically equal, the values are not actually equal" ); } } } ================================================ FILE: rust/mlt-core/src/encoder/stream/write.rs ================================================ use bytemuck::{NoUninit, cast_slice}; use fastpfor::AnyLenCodec as _; use integer_encoding::VarInt; use num_traits::PrimInt; use zigzag::ZigZag; use crate::MltError::UnsupportedPhysicalEncoding; use crate::MltResult; use crate::codecs::zigzag::{encode_zigzag, encode_zigzag_delta}; use crate::decoder::{LogicalEncoding, PhysicalEncoding, StreamMeta, StreamType}; use crate::encoder::Encoder; use crate::encoder::model::StreamCtx; use crate::encoder::stream::codecs::{LogicalCodecs, PhysicalCodecs}; use crate::encoder::stream::logical::apply_rle; use crate::encoder::stream::physical::PhysicalEncoder; use crate::encoder::writer::AltSession; #[inline] pub(crate) fn write_stream_payload( data: &mut Vec, meta: StreamMeta, is_boolean: bool, payload: &[u8], ) -> MltResult<()> { let byte_length = u32::try_from(payload.len())?; meta.write_to(data, is_boolean, byte_length)?; data.extend_from_slice(payload); Ok(()) } pub(crate) trait PhysicalIntStreamKind { type Value: Into + NoUninit + PrimInt; const FASTPFOR_ALLOWED: bool; #[cfg(target_endian = "little")] fn none<'a>(_physical: &'a mut PhysicalCodecs, values: &'a [Self::Value]) -> &'a [u8] { cast_slice(values) } #[cfg(not(target_endian = "little"))] fn none<'a>(physical: &'a mut PhysicalCodecs, values: &'a [Self::Value]) -> &'a [u8] { compile_error!("PhysicalEncoding::none is not implemented for non-little-endian targets"); } fn fastpfor<'a>( physical: &'a mut PhysicalCodecs, values: &'a [Self::Value], ) -> MltResult<&'a [u8]>; } impl PhysicalCodecs { pub(crate) fn varint(&mut self, values: &[T]) -> &[u8] where T: Copy + Into, { // encode_var writes to a stack buffer; avoids the Vec allocation // that encode_var_vec() would produce for every value. self.u8_tmp.clear(); let mut buf = [0u8; 10]; for &v in values { let n = v.into().encode_var(&mut buf); self.u8_tmp.extend_from_slice(&buf[..n]); } &self.u8_tmp } pub(crate) fn fastpfor(&mut self, values: &[u32]) -> MltResult<&[u8]> { self.u8_tmp.clear(); if !values.is_empty() { self.u32_tmp.clear(); self.fastpfor.encode(values, &mut self.u32_tmp)?; for word in &mut self.u32_tmp { *word = word.to_be(); } self.u8_tmp.extend_from_slice(cast_slice(&self.u32_tmp)); } Ok(&self.u8_tmp) } /// Physically encode and write stream to the output. pub(crate) fn write_encoded_as( &mut self, ctx: &StreamCtx, enc: &mut Encoder, le: LogicalEncoding, values: &[P::Value], encode_as: PhysicalEncoder, ) -> MltResult<()> { use PhysicalEncoding as PE; let (pe, vals) = match encode_as { PhysicalEncoder::None => (PE::None, P::none(self, values)), PhysicalEncoder::VarInt => (PE::VarInt, self.varint(values)), PhysicalEncoder::FastPFOR => (PE::FastPFor256, P::fastpfor(self, values)?), }; let meta = StreamMeta::new2(ctx.stream_type, le, pe, values.len())?; write_stream_payload(&mut enc.data, meta, false, vals) } pub(crate) fn write_alternatives( &mut self, alt: &mut AltSession<'_>, values: &[P::Value], logical: LogicalEncoding, stream_type: StreamType, ) -> MltResult<()> { use PhysicalEncoding as PE; if P::FASTPFOR_ALLOWED { alt.with(|enc| { let meta = StreamMeta::new2(stream_type, logical, PE::FastPFor256, values.len())?; write_stream_payload(&mut enc.data, meta, false, P::fastpfor(self, values)?) })?; } alt.with(|enc| { let meta = StreamMeta::new2(stream_type, logical, PE::VarInt, values.len())?; write_stream_payload(&mut enc.data, meta, false, self.varint(values)) }) } } impl PhysicalIntStreamKind for [u32] { type Value = u32; const FASTPFOR_ALLOWED: bool = true; fn fastpfor<'a>( physical: &'a mut PhysicalCodecs, values: &'a [Self::Value], ) -> MltResult<&'a [u8]> { physical.fastpfor(values) } } impl PhysicalIntStreamKind for [u64] { type Value = u64; const FASTPFOR_ALLOWED: bool = false; fn fastpfor<'a>( _physical: &'a mut PhysicalCodecs, _values: &'a [Self::Value], ) -> MltResult<&'a [u8]> { Err(UnsupportedPhysicalEncoding("FastPFOR on u64"))? } } pub(crate) trait LogicalIntStreamKind { type Input; type Output: PhysicalIntStreamKind + ?Sized; type Profile: ZigZag::Value>; } pub(crate) trait LogicalIntCodec { fn none<'a>(&'a mut self, values: &'a T) -> &'a [::Value]; fn delta<'a>(&'a mut self, values: &'a T) -> &'a [::Value]; fn rle<'a>( &'a mut self, values: &'a T, ) -> MltResult<( LogicalEncoding, &'a [::Value], )>; fn delta_rle<'a>( &'a mut self, values: &'a T, ) -> MltResult<( LogicalEncoding, &'a [::Value], )>; } fn encode_u8_as_u32<'a>(values: &[u8], target: &'a mut Vec) -> &'a [u32] { target.clear(); target.extend(values.iter().map(|&v| u32::from(v))); target } fn encode_i8_zigzag<'a>(values: &[i8], target: &'a mut Vec) -> &'a [u32] { target.clear(); target.extend(values.iter().map(|&v| i32::encode(i32::from(v)))); target } fn encode_u8_delta<'a>(values: &[u8], target: &'a mut Vec) -> &'a [u32] { target.clear(); target.reserve(values.len()); let mut prev = 0_i32; for &v in values { let v = i32::from(v); target.push(i32::encode(v.wrapping_sub(prev))); prev = v; } target } fn encode_i8_delta<'a>(values: &[i8], target: &'a mut Vec) -> &'a [u32] { target.clear(); target.reserve(values.len()); let mut prev = 0_i32; for &v in values { let v = i32::from(v); target.push(i32::encode(v.wrapping_sub(prev))); prev = v; } target } impl LogicalIntStreamKind for [u8] { type Input = u8; type Output = [u32]; type Profile = i32; } impl LogicalIntCodec<[u8]> for LogicalCodecs { fn none<'a>(&'a mut self, values: &'a [u8]) -> &'a [u32] { encode_u8_as_u32(values, &mut self.u32_tmp) } fn delta<'a>(&'a mut self, values: &'a [u8]) -> &'a [u32] { encode_u8_delta(values, &mut self.u32_tmp) } fn rle<'a>(&'a mut self, values: &'a [u8]) -> MltResult<(LogicalEncoding, &'a [u32])> { let data = encode_u8_as_u32(values, &mut self.u32_tmp); let meta = apply_rle(data, values.len(), &mut self.u32_tmp2)?; Ok((LogicalEncoding::Rle(meta), &self.u32_tmp2)) } fn delta_rle<'a>(&'a mut self, values: &'a [u8]) -> MltResult<(LogicalEncoding, &'a [u32])> { let data = encode_u8_delta(values, &mut self.u32_tmp); let meta = apply_rle(data, values.len(), &mut self.u32_tmp2)?; Ok((LogicalEncoding::DeltaRle(meta), &self.u32_tmp2)) } } impl LogicalIntStreamKind for [i8] { type Input = i8; type Output = [u32]; type Profile = i32; } impl LogicalIntCodec<[i8]> for LogicalCodecs { fn none<'a>(&'a mut self, values: &'a [i8]) -> &'a [u32] { encode_i8_zigzag(values, &mut self.u32_tmp) } fn delta<'a>(&'a mut self, values: &'a [i8]) -> &'a [u32] { encode_i8_delta(values, &mut self.u32_tmp) } fn rle<'a>(&'a mut self, values: &'a [i8]) -> MltResult<(LogicalEncoding, &'a [u32])> { let data = encode_i8_zigzag(values, &mut self.u32_tmp); let meta = apply_rle(data, values.len(), &mut self.u32_tmp2)?; Ok((LogicalEncoding::Rle(meta), &self.u32_tmp2)) } fn delta_rle<'a>(&'a mut self, values: &'a [i8]) -> MltResult<(LogicalEncoding, &'a [u32])> { let data = encode_i8_delta(values, &mut self.u32_tmp); let meta = apply_rle(data, values.len(), &mut self.u32_tmp2)?; Ok((LogicalEncoding::DeltaRle(meta), &self.u32_tmp2)) } } impl LogicalIntStreamKind for [u32] { type Input = u32; type Output = [u32]; type Profile = i32; } impl LogicalIntCodec<[u32]> for LogicalCodecs { fn none<'a>(&'a mut self, values: &'a [u32]) -> &'a [u32] { values } fn delta<'a>(&'a mut self, values: &'a [u32]) -> &'a [u32] { encode_zigzag_delta(cast_slice::(values), &mut self.u32_tmp) } fn rle<'a>(&'a mut self, values: &'a [u32]) -> MltResult<(LogicalEncoding, &'a [u32])> { let meta = apply_rle(values, values.len(), &mut self.u32_tmp)?; Ok((LogicalEncoding::Rle(meta), &self.u32_tmp)) } fn delta_rle<'a>(&'a mut self, values: &'a [u32]) -> MltResult<(LogicalEncoding, &'a [u32])> { let data = encode_zigzag_delta(cast_slice::(values), &mut self.u32_tmp); let meta = apply_rle(data, values.len(), &mut self.u32_tmp2)?; Ok((LogicalEncoding::DeltaRle(meta), &self.u32_tmp2)) } } impl LogicalIntStreamKind for [i32] { type Input = i32; type Output = [u32]; type Profile = i32; } impl LogicalIntCodec<[i32]> for LogicalCodecs { fn none<'a>(&'a mut self, values: &'a [i32]) -> &'a [u32] { encode_zigzag(values, &mut self.u32_tmp) } fn delta<'a>(&'a mut self, values: &'a [i32]) -> &'a [u32] { encode_zigzag_delta(values, &mut self.u32_tmp) } fn rle<'a>(&'a mut self, values: &'a [i32]) -> MltResult<(LogicalEncoding, &'a [u32])> { let data = encode_zigzag(values, &mut self.u32_tmp); let meta = apply_rle(data, values.len(), &mut self.u32_tmp2)?; Ok((LogicalEncoding::Rle(meta), &self.u32_tmp2)) } fn delta_rle<'a>(&'a mut self, values: &'a [i32]) -> MltResult<(LogicalEncoding, &'a [u32])> { let data = encode_zigzag_delta(values, &mut self.u32_tmp); let meta = apply_rle(data, values.len(), &mut self.u32_tmp2)?; Ok((LogicalEncoding::DeltaRle(meta), &self.u32_tmp2)) } } impl LogicalIntStreamKind for [u64] { type Input = u64; type Output = [u64]; type Profile = i64; } impl LogicalIntCodec<[u64]> for LogicalCodecs { fn none<'a>(&'a mut self, values: &'a [u64]) -> &'a [u64] { values } fn delta<'a>(&'a mut self, values: &'a [u64]) -> &'a [u64] { encode_zigzag_delta(cast_slice::(values), &mut self.u64_tmp) } fn rle<'a>(&'a mut self, values: &'a [u64]) -> MltResult<(LogicalEncoding, &'a [u64])> { let meta = apply_rle(values, values.len(), &mut self.u64_tmp)?; Ok((LogicalEncoding::Rle(meta), &self.u64_tmp)) } fn delta_rle<'a>(&'a mut self, values: &'a [u64]) -> MltResult<(LogicalEncoding, &'a [u64])> { let data = encode_zigzag_delta(cast_slice::(values), &mut self.u64_tmp); let meta = apply_rle(data, values.len(), &mut self.u64_tmp2)?; Ok((LogicalEncoding::DeltaRle(meta), &self.u64_tmp2)) } } impl LogicalIntStreamKind for [i64] { type Input = i64; type Output = [u64]; type Profile = i64; } impl LogicalIntCodec<[i64]> for LogicalCodecs { fn none<'a>(&'a mut self, values: &'a [i64]) -> &'a [u64] { encode_zigzag(values, &mut self.u64_tmp) } fn delta<'a>(&'a mut self, values: &'a [i64]) -> &'a [u64] { encode_zigzag_delta(values, &mut self.u64_tmp) } fn rle<'a>(&'a mut self, values: &'a [i64]) -> MltResult<(LogicalEncoding, &'a [u64])> { let data = encode_zigzag(values, &mut self.u64_tmp); let meta = apply_rle(data, values.len(), &mut self.u64_tmp2)?; Ok((LogicalEncoding::Rle(meta), &self.u64_tmp2)) } fn delta_rle<'a>(&'a mut self, values: &'a [i64]) -> MltResult<(LogicalEncoding, &'a [u64])> { let data = encode_zigzag_delta(values, &mut self.u64_tmp); let meta = apply_rle(data, values.len(), &mut self.u64_tmp2)?; Ok((LogicalEncoding::DeltaRle(meta), &self.u64_tmp2)) } } ================================================ FILE: rust/mlt-core/src/encoder/tests.rs ================================================ use crate::TileLayer; use crate::encoder::model::ColumnKind; use crate::encoder::{ExplicitEncoder, IntEncoder, SortStrategy, StagedLayer, VertexBufferType}; impl ExplicitEncoder { /// Use `enc` for all integer streams, plain string encoding, and `Vec2` vertex layout. #[must_use] pub fn all(enc: IntEncoder) -> Self { Self { vertex_buffer_type: VertexBufferType::Vec2, force_stream: Box::new(|_| false), get_int_encoder: Box::new(move |_| enc), get_str_encoding: Box::new(|_| crate::encoder::StrEncoding::Plain), } } /// Like [`Self::all`] but use `str_enc` for string property columns. #[must_use] pub fn all_with_str(enc: IntEncoder, str_enc: crate::encoder::StrEncoding) -> Self { Self { get_str_encoding: Box::new(move |_| str_enc), ..Self::all(enc) } } /// Use `id_enc` for the ID stream; `varint` for all other streams. /// /// Useful for tests that need to pin the exact ID encoding without caring about /// geometry or property streams. #[must_use] pub fn for_id(id_enc: IntEncoder) -> Self { Self { get_int_encoder: Box::new(move |ctx| { if ctx.kind == ColumnKind::Id { id_enc } else { IntEncoder::varint() } }), ..Self::all(IntEncoder::varint()) } } } #[must_use] pub fn stage_tile( tile: TileLayer, sort: SortStrategy, allow_shared_dict: bool, tessellate: bool, ) -> StagedLayer { let analysis = tile.analyze(allow_shared_dict).expect("analyze tile"); let curve_params = tile.curve_params(); StagedLayer::from_tile(tile, sort, &analysis, tessellate, curve_params) } ================================================ FILE: rust/mlt-core/src/encoder/tile.rs ================================================ //! Row-oriented "source form" for the optimizer. //! //! [`TileLayer`] holds one [`TileFeature`] per map feature, each owning //! its geometry as a [`geo_types::Geometry`] and its property values as a //! plain `Vec`. This is the working form used throughout the //! optimizer and sorting pipeline: it is cheap to clone, trivially sortable, //! and free from any encoded/decoded duality. //! //! Conversion from [`TileLayer`] to [`StagedLayer`] is done via //! [`StagedLayer::from_tile`] with pre-computed layer statistics. use crate::decoder::{GeometryValues, PropValue, TileFeature, TileLayer}; use crate::encoder::model::{CurveParams, StagedLayer}; use crate::encoder::optimizer::{LayerStats, Presence, SharedDictRole}; use crate::encoder::{SortStrategy, StagedId, StagedProperty, StagedSharedDict}; impl StagedLayer { /// Construct a [`StagedLayer`] from a row-oriented [`TileLayer`] using /// pre-computed layer statistics and curve parameters. /// /// `curve_params` is taken as a parameter (rather than recomputed here) /// so a single [`TileLayer::curve_params`] scan feeds every sort trial /// and the encoder's dictionary builders. /// /// When `tessellate` is `true`, polygon and multi-polygon geometries have /// their triangulation stored alongside the geometry. #[must_use] #[hotpath::measure] pub fn from_tile( mut source: TileLayer, sort: SortStrategy, stats: &LayerStats, tessellate: bool, curve_params: CurveParams, ) -> Self { assert!(!source.features.is_empty(), "empty tile"); source.sort(sort, curve_params); let mut geometry = if tessellate { GeometryValues::new_tessellated() } else { GeometryValues::default() }; for f in &source.features { geometry.push_geom(&f.geometry); } let id = StagedId::from_optional_with_presence( source.features.iter().map(|f| f.id), stats.id.as_ref(), ); let shared_dict_columns = shared_dict_columns(stats); let mut properties = Vec::with_capacity(source.property_names.len()); for (col_idx, shared_cols) in shared_dict_columns .iter() .enumerate() .take(source.property_names.len()) { let prop_analysis = stats .properties .get(col_idx) .expect("analysis matches source property columns"); match prop_analysis.stats.shared_dict() { SharedDictRole::Owner(prefix) => { properties.push(build_shared_dict( col_idx, &prefix, shared_cols, &source.property_names, stats, &mut source.features, )); } SharedDictRole::Member(_) => {} SharedDictRole::None => { if let Some(prop) = build_scalar_column( std::mem::take(&mut source.property_names[col_idx]), col_idx, prop_analysis.presence, &mut source.features, ) { properties.push(prop); } } } } Self { name: source.name, extent: source.extent, id, geometry, properties, } } } fn shared_dict_columns(stats: &LayerStats) -> Vec> { let mut columns = vec![Vec::new(); stats.properties.len()]; for (col_idx, prop) in stats.properties.iter().enumerate() { match prop.stats.shared_dict() { SharedDictRole::Owner(_) => columns[col_idx].push(col_idx), SharedDictRole::Member(owner_col) => columns[owner_col].push(col_idx), SharedDictRole::None => {} } } columns } fn build_scalar_column( name: String, col: usize, presence: Presence, features: &mut [TileFeature], ) -> Option { if presence == Presence::AllNull { return None; } // Determine the variant by peeking at the first feature value. // Typed nulls (e.g. `PropValue::Bool(None)`) already carry the column type, // so no filtering is needed; only a fully-absent column returns `None` here. // Fall back to `Str` if every feature has no value for this column. let first_val = features.iter().find_map(|f| f.properties.get(col)); // Presence is precomputed before sort trials; this pass only gathers values // in the selected row order. macro_rules! scalar_col { ($opt_ctor:ident, $non_opt_ctor:ident, $ty:ty, $sv:ident) => {{ Some(match presence { Presence::AllNull => unreachable!("handled before variant dispatch"), Presence::AllPresent => StagedProperty::$non_opt_ctor( name, features .iter() .map(|f| match f.properties.get(col) { Some(PropValue::$sv(Some(v))) => *v, _ => unreachable!("analysis guarantees present typed values"), }) .collect(), ), Presence::Mixed | Presence::SameAsProp(_) => StagedProperty::$opt_ctor( name, features.iter().map(|f| match f.properties.get(col) { Some(PropValue::$sv(v)) => *v, _ => None, }), ), }) }}; } match first_val { Some(PropValue::Bool(_)) => scalar_col!(opt_bool, bool, bool, Bool), Some(PropValue::I8(_)) => scalar_col!(opt_i8, i8, i8, I8), Some(PropValue::U8(_)) => scalar_col!(opt_u8, u8, u8, U8), Some(PropValue::I32(_)) => scalar_col!(opt_i32, i32, i32, I32), Some(PropValue::U32(_)) => scalar_col!(opt_u32, u32, u32, U32), Some(PropValue::I64(_)) => scalar_col!(opt_i64, i64, i64, I64), Some(PropValue::U64(_)) => scalar_col!(opt_u64, u64, u64, U64), Some(PropValue::F32(_)) => scalar_col!(opt_f32, f32, f32, F32), Some(PropValue::F64(_)) => scalar_col!(opt_f64, f64, f64, F64), Some(PropValue::Str(_)) | None => Some(match presence { Presence::AllNull => unreachable!("handled before variant dispatch"), Presence::AllPresent => StagedProperty::str( name, features .iter_mut() .map(|f| match f.properties.get_mut(col) { Some(PropValue::Str(Some(v))) => std::mem::take(v), _ => unreachable!("analysis guarantees present string values"), }), ), Presence::Mixed | Presence::SameAsProp(_) => StagedProperty::opt_str( name, features .iter_mut() .map(|f| match f.properties.get_mut(col) { Some(PropValue::Str(v)) => v.take(), _ => None, }), ), }), } } fn build_shared_dict( owner_col: usize, prefix: &str, shared_dict_columns: &[usize], property_names: &[String], analysis: &LayerStats, features: &mut [TileFeature], ) -> StagedProperty { debug_assert_eq!(shared_dict_columns.first(), Some(&owner_col)); let columns = shared_dict_columns.iter().copied().map(|col_idx| { let name = &property_names[col_idx]; let suffix = name.strip_prefix(prefix).unwrap_or(name).to_owned(); let values: Vec> = features .iter_mut() .map(|f| match f.properties.get_mut(col_idx) { Some(PropValue::Str(s)) => s.take(), _ => None, }) .collect(); let presence = analysis.properties[col_idx].presence; (suffix, values, presence) }); StagedProperty::SharedDict( StagedSharedDict::new(prefix.to_owned(), columns).expect("StagedSharedDict succeed"), ) } #[cfg(test)] mod tests { use geo_types::Point; use super::*; use crate::Layer; use crate::decoder::GeometryValues; use crate::encoder::{Codecs, Encoder}; use crate::test_helpers::{dec, parser}; fn layer_tile(staged: StagedLayer) -> TileLayer { let mut codecs = Codecs::default(); let buf = staged .encode_into(Encoder::default(), &mut codecs) .unwrap() .into_layer_bytes() .unwrap(); let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap(); let Layer::Tag01(lazy) = layer else { panic!() }; let mut d = dec(); lazy.decode_all(&mut d).unwrap().into_tile(&mut d).unwrap() } fn two_points() -> GeometryValues { let mut g = GeometryValues::default(); g.push_geom(&geo_types::Geometry::::Point(Point::new(0, 0))); g.push_geom(&geo_types::Geometry::::Point(Point::new(1, 1))); g } /// `into_tile` must produce a **typed** null (e.g. `PropValue::Bool(None)`) /// for null slots, matching the column's actual type, even when the **first** /// feature is null. #[test] fn null_first_feature_preserves_later_typed_value() { let tile = layer_tile(StagedLayer { name: "t".into(), extent: 4096, id: StagedId::None, geometry: two_points(), properties: vec![StagedProperty::opt_bool("flag", vec![None, Some(false)])], }); assert_eq!(tile.property_names, vec!["flag"]); // Null slot → typed null matching the column type assert_eq!(tile.features[0].properties[0], PropValue::Bool(None)); // Non-null value after the null must not be dropped assert_eq!(tile.features[1].properties[0], PropValue::Bool(Some(false))); } /// Every scalar type must produce a typed null for null slots and a typed /// non-null value for present slots, even when the first feature is null. #[test] fn null_first_feature_across_types() { let props = vec![ StagedProperty::opt_bool("b", vec![None, Some(true)]), StagedProperty::opt_i8("i8", vec![None, Some(-1)]), StagedProperty::opt_u8("u8", vec![None, Some(2)]), StagedProperty::opt_i32("i32", vec![None, Some(-3)]), StagedProperty::opt_u32("u32", vec![None, Some(4)]), StagedProperty::opt_i64("i64", vec![None, Some(-5)]), StagedProperty::opt_u64("u64", vec![None, Some(6)]), StagedProperty::opt_f32("f32", vec![None, Some(7.0)]), StagedProperty::opt_f64("f64", vec![None, Some(8.0)]), StagedProperty::opt_str("s", vec![None, Some("ok")]), ]; let tile = layer_tile(StagedLayer { name: "t".into(), extent: 4096, id: StagedId::None, geometry: two_points(), properties: props, }); // Feature 0: every column is null → typed null for each column let n = &tile.features[0].properties; assert_eq!(n[0], PropValue::Bool(None)); assert_eq!(n[1], PropValue::I8(None)); assert_eq!(n[2], PropValue::U8(None)); assert_eq!(n[3], PropValue::I32(None)); assert_eq!(n[4], PropValue::U32(None)); assert_eq!(n[5], PropValue::I64(None)); assert_eq!(n[6], PropValue::U64(None)); assert_eq!(n[7], PropValue::F32(None)); assert_eq!(n[8], PropValue::F64(None)); assert_eq!(n[9], PropValue::Str(None)); // Feature 1: every column has its typed non-null value let p = &tile.features[1].properties; assert_eq!(p[0], PropValue::Bool(Some(true))); assert_eq!(p[1], PropValue::I8(Some(-1))); assert_eq!(p[2], PropValue::U8(Some(2))); assert_eq!(p[3], PropValue::I32(Some(-3))); assert_eq!(p[4], PropValue::U32(Some(4))); assert_eq!(p[5], PropValue::I64(Some(-5))); assert_eq!(p[6], PropValue::U64(Some(6))); assert_eq!(p[7], PropValue::F32(Some(7.0))); assert_eq!(p[8], PropValue::F64(Some(8.0))); assert_eq!(p[9], PropValue::Str(Some("ok".into()))); } } ================================================ FILE: rust/mlt-core/src/encoder/unknown.rs ================================================ use integer_encoding::VarIntWriter as _; use crate::MltResult; use crate::decoder::Unknown; use crate::encoder::Encoder; use crate::encoder::model::EncodedUnknown; use crate::utils::{BinarySerializer as _, checked_sum2}; impl EncodedUnknown { /// Serialize an unknown layer record directly to [`enc.data`](Encoder::data). /// /// Writes the complete `[varint(size)][tag][value]` record — the bytes are /// already in wire format so no `hdr`/`meta` split is needed. pub fn write_to(&self, mut enc: Encoder) -> MltResult { let buffer_len = u32::try_from(self.value.len())?; let size = checked_sum2(buffer_len, 1)?; enc.write_varint(size)?; enc.write_u8(self.tag)?; enc.data.extend_from_slice(&self.value); Ok(enc) } } impl<'a> From> for EncodedUnknown { fn from(u: Unknown<'a>) -> Self { Self { tag: u.tag, value: u.value.to_vec(), } } } #[cfg(all(not(test), feature = "arbitrary"))] impl arbitrary::Arbitrary<'_> for EncodedUnknown { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let mut tag: u8 = u.arbitrary()?; // Tag 1 is the known Tag01 format; producing it as Unknown would break round-trip-ability if tag == 1 { tag = 0; } Ok(Self { tag, value: u.arbitrary()?, }) } } ================================================ FILE: rust/mlt-core/src/encoder/writer.rs ================================================ use std::collections::HashMap; use std::{io, mem}; use fsst::Compressor; use integer_encoding::VarIntWriter as _; use crate::decoder::{ColumnType, Morton}; use crate::encoder::model::{CurveParams, ExplicitEncoder, StrEncoding, StreamCtx}; use crate::encoder::{EncoderConfig, IntEncoder, VertexBufferType}; use crate::utils::BinarySerializer as _; use crate::{MltError, MltResult}; /// Stateful encoder that accumulates encoded layer bytes. /// /// Logical temporary buffers live in `Codecs` and are passed alongside /// the encoder while a stream is being transformed and serialized. Physical /// encoders live here with their own scratch buffers, then copy complete /// payloads into [`data`](Encoder::data). /// /// # Buffer layout /// /// The MLT layer wire format is: /// /// ```text /// [varint(body_len + 1)] [tag = 1] /// [name: string] [extent: varint] [column_count: varint] <- hdr /// [col_type₁] [col_type₂] … [col_typeN] <- meta /// [col₁ stream data] [col₂ stream data] … [colN stream data] <- data /// ``` /// /// The three sections are accumulated into separate buffers so they can be /// combined at the end *without* any in-place insertion or extra copies: /// /// * [`hdr`] – layer header (name, extent, `column_count`). /// * [`meta`] – column-type bytes (one byte + optional name per column). /// * [`data`] – encoded stream data; also the target of [`impl Write`]. /// /// # Sort-strategy trialing /// /// Create one `Encoder` per sort-strategy trial, encode the layer into it, /// and keep the one whose `total_len()` is smallest: /// /// ```rust,ignore /// let mut codecs = Codecs::default(); /// let mut best: Option = None; /// for strategy in strategies { /// let mut enc = Encoder::new(cfg); /// layer.write_to(&mut enc, &mut codecs)?; /// if best.as_ref().is_none_or(|b| enc.total_len() < b.total_len()) { /// best = Some(enc); /// } /// } /// return best.unwrap().into_layer_bytes(); /// ``` /// /// # Stream-level encoding alternatives /// /// Use [`Encoder::try_alternatives`] to open a competition, /// then submit each candidate via `AltSession::with`. The guard's `Drop` /// impl finalises the competition automatically: /// /// ```rust,ignore /// let mut alt = enc.try_alternatives(); /// alt.with(|enc| write_stream_as_varint(data, enc))?; /// alt.with(|enc| write_stream_as_fastpfor(data, enc))?; /// // alt drops → keeps whichever was shorter /// ``` /// /// [`hdr`]: Encoder::hdr /// [`meta`]: Encoder::meta /// [`data`]: Encoder::data /// [`impl Write`]: Encoder#impl-Write #[derive(Default)] pub struct Encoder { /// Encoding configuration: controls which optimization strategies are tried /// (sort orders, compression algorithms, etc.). /// /// Set once at construction time via [`Encoder::new`]; propagated /// automatically to all sub-encoders so individual encode methods do not /// need a separate `cfg` argument. pub cfg: EncoderConfig, /// When [`Some`], property / ID / geometry encoders use `ExplicitEncoder` /// callbacks instead of trying candidate encodings. When [`None`], the /// automatic optimization path runs. pub(crate) explicit: Option, /// Layer header bytes: `name`, `extent`, `column_count`. /// /// Written to `hdr` via [`Encoder::write_header`]. This section comes /// first in the wire format and is never subject to alternatives. pub hdr: Vec, /// Column-type metadata bytes. /// /// Each column contributes one type byte (plus a name string for property /// columns). Written by the `write_columns_meta_to` methods, which write /// directly to `enc.meta`. This section comes second in the wire format /// and is never subject to alternatives (column types are fixed). pub meta: Vec, /// Encoded stream data. /// /// All stream counts, per-stream encoding-metadata bytes, and encoded /// data bytes land here via [`impl Write`]. This section comes last in /// the wire format and is where stream-level alternatives compete. /// /// [`impl Write`]: Encoder#impl-Write pub data: Vec, /// Morton parameters for this layer's vertex set; `None` if the extent /// exceeds 16 bits per axis (Morton encoding is unusable in that case). /// Pre-populated by [`StagedLayer::encode_into`](crate::encoder::StagedLayer::encode_into). pub(crate) morton_cache: Option, /// Hilbert curve parameters for this layer's vertex set. Pre-populated by /// [`StagedLayer::encode_into`](crate::encoder::StagedLayer::encode_into). pub(crate) hilbert_cache: Option, /// Cached FSST compressor per string column, keyed by column name. /// `None` means training found FSST not viable for that column. /// Trained on deduplicated values on the first sort trial, reused on subsequent trials. pub(crate) fsst_cache: HashMap>, // ----------------------------------------------------------------------- // Alternatives state — a stack that supports nested competitions. // // Invariant between candidates at any level: // data.len() == level.data_start + level.best_data_size.unwrap_or(0) // meta.len() == level.meta_start + level.best_meta_size.unwrap_or(0) // // Empty stack ↔ no competition in progress. // ----------------------------------------------------------------------- /// Stack of active encoding competitions, innermost last. /// /// Empty while no [`Encoder::try_alternatives`] session /// is in progress. alt_stack: Vec, } impl Encoder { /// Create a new encoder with the given [`EncoderConfig`]. /// /// Use [`Encoder::default()`] when the default configuration is sufficient. #[inline] #[must_use] pub fn new(cfg: EncoderConfig) -> Self { Self { cfg, ..Self::default() } } /// Like [`Self::new`] but with the explicit encoder set for deterministic encoding /// (tests, synthetics). Use with `StagedLayer::encode_explicit`. #[inline] #[must_use] pub fn with_explicit(cfg: EncoderConfig, explicit: ExplicitEncoder) -> Self { Self { cfg, explicit: Some(explicit), ..Self::default() } } /// Ensure this encoder is in the good state, and moves results to a new instance. /// This allows current instance to be reused for other experiment, avoiding repeat of some operations. #[must_use] pub(crate) fn preserve_results(&mut self) -> Self { assert_eq!(self.alt_stack.len(), 0, "Alternatives stack is not empty"); Self { cfg: EncoderConfig::default(), explicit: None, hdr: mem::take(&mut self.hdr), meta: mem::take(&mut self.meta), data: mem::take(&mut self.data), morton_cache: None, hilbert_cache: None, fsst_cache: HashMap::new(), alt_stack: vec![], } } #[inline] pub(crate) fn write_column_type(&mut self, column_type: ColumnType) -> MltResult<()> { column_type.write_to(&mut self.meta).map_err(MltError::from) } #[inline] pub(crate) fn write_column_name(&mut self, name: &str) -> MltResult<()> { self.meta.write_string(name).map_err(MltError::from) } #[inline] pub(crate) fn write_column_header( &mut self, column_type: ColumnType, name: &str, ) -> MltResult<()> { self.write_column_type(column_type)?; self.write_column_name(name) } /// Write the layer header (`name`, `extent`, `column_count`) to [`hdr`]. /// /// Must be called exactly once per layer, after all column meta and data. /// /// [`hdr`]: Encoder::hdr #[hotpath::measure] pub fn write_header(&mut self, name: &str, extent: u32, column_count: usize) -> MltResult<()> { debug_assert!( self.alt_stack.is_empty(), "write_header called with an open alternatives session" ); let name_len = u32::try_from(name.len())?; let column_count = u32::try_from(column_count)?; self.hdr.write_varint(name_len).map_err(MltError::from)?; self.hdr.extend_from_slice(name.as_bytes()); self.hdr.write_varint(extent).map_err(MltError::from)?; self.hdr .write_varint(column_count) .map_err(MltError::from)?; Ok(()) } /// When [`Self::explicit`] is [`Some`], returns the callback-chosen [`IntEncoder`]. /// [`None`] means run automatic candidate selection for that stream. #[inline] pub(crate) fn override_int_enc(&self, ctx: &StreamCtx<'_>) -> Option { self.explicit.as_ref().map(|e| (e.get_int_encoder)(ctx)) } /// When [`Self::explicit`] is [`Some`], returns the callback-chosen [`StrEncoding`]. /// [`None`] means run automatic string / shared-dict corpus selection. #[inline] pub(crate) fn override_str_enc(&self, name: &str) -> Option { self.explicit.as_ref().map(|e| (e.get_str_encoding)(name)) } /// Pinned vertex layout when an explicit encoder is active. #[inline] #[allow(clippy::unused_self)] pub(crate) fn override_vertex_buffer_type(&self) -> Option { self.explicit.as_ref().map(|e| e.vertex_buffer_type) } /// Whether to force writing a geometry stream even when its data is empty. /// /// Delegates to [`ExplicitEncoder::force_stream`]; returns `false` when no explicit /// encoder is active (the default "skip empty streams" behavior). #[inline] pub(crate) fn force_stream(&self, ctx: &StreamCtx<'_>) -> bool { self.explicit .as_ref() .is_some_and(|e| (e.force_stream)(ctx)) } /// Total encoded bytes across all three sections (`hdr + meta + data`). #[inline] #[must_use] pub fn total_len(&self) -> usize { self.hdr.len() + self.meta.len() + self.data.len() } /// Concatenate `hdr + meta + data` into a single buffer **without** a /// tag/size prefix. /// /// Use this when the caller expects raw layer body bytes (without the size/tag framing) /// rather than a complete framed wire record — see [`Self::into_layer_bytes`] for the framed form. #[must_use] pub fn into_raw_bytes(mut self) -> Vec { let mut out = Vec::with_capacity(self.hdr.len() + self.meta.len() + self.data.len()); out.append(&mut self.hdr); out.append(&mut self.meta); out.append(&mut self.data); out } /// Assemble the complete Tag-01 layer record. pub fn into_layer_bytes(self) -> MltResult> { self.into_layer_bytes_with_tag(1) } /// Assemble a complete layer record for the given `tag`: /// `[varint(body_len + 1)][tag][hdr][meta][data]`. fn into_layer_bytes_with_tag(mut self, tag: u8) -> MltResult> { debug_assert!( self.alt_stack.is_empty(), "into_layer_bytes_with_tag called with an open alternatives session" ); let body_len = self.hdr.len() + self.meta.len() + self.data.len(); let size = u32::try_from(body_len + 1)?; // +1 for the tag byte let mut out = Vec::with_capacity(5 + 1 + body_len); out.write_varint(size).map_err(MltError::from)?; out.push(tag); out.append(&mut self.hdr); out.append(&mut self.meta); out.append(&mut self.data); Ok(out) } /// Begin a new encoding competition. /// /// Returns an `AltSession` guard. Submit each candidate via /// `AltSession::with`; the guard's `Drop` impl finalises /// the competition and retains the shortest candidate automatically. /// /// Nesting is supported: calling `try_alternatives` inside a /// `with` closure opens an inner competition on the same stack, /// resolved before the outer candidate is committed. /// /// # Example /// /// ```rust,ignore /// let mut alt = enc.try_alternatives(); /// for cand in candidates { /// alt.with(|enc| write_candidate(cand, enc))?; /// } /// // alt drops → finalises the competition /// ``` pub fn try_alternatives(&mut self) -> AltSession<'_> { self.alt_stack.push(AltLevel { data_start: self.data.len(), meta_start: self.meta.len(), best_data: None, best_meta: None, }); AltSession { enc: self } } /// Commit the current candidate at the innermost competition level. /// /// Compares bytes written since the last commit against the running best /// by **total** (`data + meta`) size; keeps the shorter one. /// /// Called internally by `AltSession::with` on `Ok`. fn alt_commit(&mut self) { debug_assert!( !self.alt_stack.is_empty(), "alt_commit called outside an active AltSession" ); let (data, meta, stack) = (&mut self.data, &mut self.meta, &mut self.alt_stack); let level = stack.last_mut().unwrap(); Self::close_candidate(data, meta, level); } /// Finalize the innermost competition and pop it from the stack. /// /// Any bytes written since the last `alt_commit` are evaluated as a /// final candidate; if no pending bytes exist and a best is already /// recorded this is a cheap stack-pop. fn alt_pop(&mut self) { debug_assert!( !self.alt_stack.is_empty(), "alt_pop called outside an active AltSession" ); { let (data, meta, stack) = (&mut self.data, &mut self.meta, &mut self.alt_stack); let level = stack.last_mut().unwrap(); let data_pending = data.len() - (level.data_start + level.best_data.unwrap_or(0)); let meta_pending = meta.len() - (level.meta_start + level.best_meta.unwrap_or(0)); if data_pending > 0 || meta_pending > 0 || level.best_data.is_none() { Self::close_candidate(data, meta, level); } } self.alt_stack.pop(); } /// Shared compare-and-keep logic used by both `alt_commit` and `alt_pop`. /// /// Compares the bytes written since the last committed candidate against /// the current best by **total** (`data + meta`) size. /// Keeps the shorter one; ties preserve the existing best. fn close_candidate(data: &mut Vec, meta: &mut Vec, level: &mut AltLevel) { let best_data_end = level.data_start + level.best_data.unwrap_or(0); let best_meta_end = level.meta_start + level.best_meta.unwrap_or(0); let cand_data = data.len() - best_data_end; let cand_meta = meta.len() - best_meta_end; let cand_total = cand_data + cand_meta; let best_total = level.best_data.unwrap_or(0) + level.best_meta.unwrap_or(0); if level.best_data.is_none_or(|_| cand_total < best_total) { // New best: shift data candidate bytes to data_start. if level.best_data.is_some() { data.copy_within(best_data_end..best_data_end + cand_data, level.data_start); meta.copy_within(best_meta_end..best_meta_end + cand_meta, level.meta_start); } data.truncate(level.data_start + cand_data); meta.truncate(level.meta_start + cand_meta); level.best_data = Some(cand_data); level.best_meta = Some(cand_meta); } else { // Not an improvement: discard. data.truncate(best_data_end); meta.truncate(best_meta_end); } } } /// State for one level of an encoding competition. /// /// Tracks the starting position in both the [`data`](Encoder::data) and /// [`meta`](Encoder::meta) buffers, and the byte count of the best candidate /// committed so far. /// /// Candidates are compared by **total** bytes (`data + meta`); the shorter one /// wins, with ties resolved in favor of the earlier candidate. #[derive(Debug, Default, Clone)] struct AltLevel { data_start: usize, meta_start: usize, /// Byte count appended to `data` by the current best candidate. best_data: Option, /// Byte count appended to `meta` by the current best candidate. best_meta: Option, } /// RAII guard for a stream-encoding competition opened by [`Encoder::try_alternatives`]. /// /// Submit each candidate via [`with`](AltSession::with); on `Ok` the candidate is /// committed (compared against the running best and kept if shorter); on `Err` /// the partial write is rolled back and the error propagates. The guard's /// `Drop` impl finalises the competition automatically, so the [`Encoder`] is /// always left in a consistent state even when an error exits the loop early. /// /// Nesting is allowed: calling [`Encoder::try_alternatives`] inside a /// `with` closure opens an inner competition that is fully /// resolved before the outer candidate is committed. #[must_use = "AltSession must be used; drop it to finalise the competition"] pub struct AltSession<'a> { enc: &'a mut Encoder, } impl AltSession<'_> { /// Encode one candidate. /// /// - **`Ok`** — commits the candidate; replaces the running best if shorter. /// - **`Err`** — truncates the partial write back to the pre-call checkpoint /// and returns the error. The guard's `Drop` still finalises the /// competition cleanly using whichever candidates succeeded so far. #[hotpath::measure] pub fn with(&mut self, f: F) -> MltResult<()> where F: FnOnce(&mut Encoder) -> MltResult<()>, { let data_cp = self.enc.data.len(); let meta_cp = self.enc.meta.len(); match f(self.enc) { Ok(()) => { self.enc.alt_commit(); Ok(()) } Err(e) => { self.enc.data.truncate(data_cp); self.enc.meta.truncate(meta_cp); Err(e) } } } } impl Drop for AltSession<'_> { fn drop(&mut self) { self.enc.alt_pop(); } } /// Writes bytes to [`Encoder::data`]. /// /// This blanket implementation makes `Encoder` compatible with all /// `BinarySerializer`, `VarIntWriter`, and other `Write`-based utilities so that /// stream-data methods do not need a separate code path. impl io::Write for Encoder { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { self.data.write(buf) } #[inline] fn flush(&mut self) -> io::Result<()> { Ok(()) } #[inline] fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { self.data.write_all(buf) } } #[cfg(test)] mod tests { use super::*; /// Helper: directly extend `enc.data` with raw bytes (simulates a stream write). fn push(enc: &mut Encoder, bytes: &[u8]) { enc.data.extend_from_slice(bytes); } // ── basic single-level behavior ────────────────────────────────────── /// The shortest candidate wins. #[test] fn alternatives_keeps_shortest() { let mut enc = Encoder::default(); push(&mut enc, b"prefix"); let mut alt = enc.try_alternatives(); alt.with(|enc| { push(enc, b"longer"); Ok(()) }) .unwrap(); // 6 bytes alt.with(|enc| { push(enc, b"ab"); Ok(()) }) .unwrap(); // 2 bytes — shortest alt.with(|enc| { push(enc, b"xyz"); Ok(()) }) .unwrap(); // 3 bytes drop(alt); assert_eq!(enc.data, b"prefixab"); } /// On a tie the first candidate is kept (strict `<`, not `<=`). #[test] fn alternatives_tie_keeps_first() { let mut enc = Encoder::default(); let mut alt = enc.try_alternatives(); alt.with(|enc| { push(enc, b"aaa"); Ok(()) }) .unwrap(); // 3 bytes alt.with(|enc| { push(enc, b"bbb"); Ok(()) }) .unwrap(); // 3 bytes — equal drop(alt); assert_eq!(enc.data, b"aaa"); } /// A single candidate is unconditionally the winner. #[test] fn alternatives_single_candidate() { let mut enc = Encoder::default(); let mut alt = enc.try_alternatives(); alt.with(|enc| { push(enc, b"only"); Ok(()) }) .unwrap(); drop(alt); assert_eq!(enc.data, b"only"); } /// Bytes written before `try_alternatives` are left intact throughout. #[test] fn prefix_bytes_are_preserved() { let mut enc = Encoder::default(); push(&mut enc, b"HDR"); let mut alt = enc.try_alternatives(); alt.with(|enc| { push(enc, b"long_encoding"); Ok(()) }) .unwrap(); // 13 bytes alt.with(|enc| { push(enc, b"short"); Ok(()) }) .unwrap(); // 5 bytes — winner drop(alt); assert_eq!(&enc.data[..3], b"HDR"); assert_eq!(&enc.data[3..], b"short"); } /// Dropping the guard after all candidates are committed is a cheap stack-pop. #[test] fn drop_after_all_committed_is_noop() { let mut enc = Encoder::default(); let mut alt = enc.try_alternatives(); alt.with(|enc| { push(enc, b"best"); Ok(()) }) .unwrap(); drop(alt); // all candidates committed; drop just pops the stack assert!(enc.alt_stack.is_empty(), "stack empty after drop"); assert_eq!(enc.data, b"best"); } // ── nesting ─────────────────────────────────────────────────────────── /// An inner competition is resolved before the outer candidate is committed. #[test] fn nested_alternatives() { let mut enc = Encoder::default(); let mut outer = enc.try_alternatives(); // Outer candidate A: header bytes + inner competition. outer .with(|enc| { push(enc, b"A:"); let mut inner = enc.try_alternatives(); // inner level pushed inner.with(|enc| { push(enc, b"long_inner"); Ok(()) })?; // 10 bytes inner.with(|enc| { push(enc, b"in"); Ok(()) })?; // 2 bytes — inner winner drop(inner); // inner done; enc = b"A:in" push(enc, b"!"); Ok(()) }) .unwrap(); // outer candidate A = b"A:in!" (5 bytes) // Outer candidate B: shorter overall. outer .with(|enc| { push(enc, b"B"); Ok(()) }) .unwrap(); // 1 byte — winner drop(outer); assert_eq!(enc.data, b"B"); } /// Stack depth tracks nesting level; inner guard drops before outer closure returns. #[test] fn nesting_depth_reflected_in_stack() { let mut enc = Encoder::default(); assert_eq!(enc.alt_stack.len(), 0); let mut outer = enc.try_alternatives(); outer .with(|enc| { assert_eq!(enc.alt_stack.len(), 1); // outer level on stack let mut inner = enc.try_alternatives(); inner.with(|enc| { assert_eq!(enc.alt_stack.len(), 2); // both levels on stack push(enc, b"x"); Ok(()) })?; drop(inner); // inner popped assert_eq!(enc.alt_stack.len(), 1); push(enc, b"y"); Ok(()) }) .unwrap(); drop(outer); // outer popped assert_eq!(enc.alt_stack.len(), 0); } // ── meta buffer tracking ────────────────────────────────────────────── /// Writes to both `data` and `meta` are rolled back for the losing /// candidate and kept for the winner, measured by total bytes. #[test] fn alternatives_tracks_meta_and_data() { let mut enc = Encoder::default(); enc.data.extend_from_slice(b"D"); enc.meta.extend_from_slice(b"M"); let mut alt = enc.try_alternatives(); // Candidate A: 4 data + 2 meta = 6 total alt.with(|enc| { push(enc, b"DDDD"); enc.meta.extend_from_slice(b"mm"); Ok(()) }) .unwrap(); // Candidate B: 1 data + 1 meta = 2 total — winner alt.with(|enc| { push(enc, b"d"); enc.meta.extend_from_slice(b"n"); Ok(()) }) .unwrap(); drop(alt); assert_eq!(enc.data, b"Dd"); assert_eq!(enc.meta, b"Mn"); } // ── error rollback ──────────────────────────────────────────────────── /// A failing candidate is rolled back; prior best is preserved. #[test] fn error_candidate_is_rolled_back() { let mut enc = Encoder::default(); let mut alt = enc.try_alternatives(); alt.with(|enc| { push(enc, b"ok"); Ok(()) }) .unwrap(); let _ = alt.with(|enc| { push(enc, b"partial"); Err(MltError::IntegerOverflow) // simulated failure }); drop(alt); assert_eq!(enc.data, b"ok"); // "partial" was rolled back; "ok" kept } } ================================================ FILE: rust/mlt-core/src/errors.rs ================================================ use std::convert::Infallible; use std::num::TryFromIntError; use num_enum::TryFromPrimitiveError; use crate::decoder::{ GeometryType, LogicalEncoding, LogicalTechnique, PhysicalEncoding, StreamType, }; use crate::utils::AsUsize; pub type MltResult = Result; pub(crate) type MltRefResult<'a, T> = Result<(&'a [u8], T), MltError>; #[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum MltError { #[error("cannot decode {0} as {1}")] DataWidthMismatch(&'static str, &'static str), #[error("dictionary index {0} out of bounds (len={1})")] DictIndexOutOfBounds(u32, usize), #[error("duplicate value found where unique required")] DuplicateValue, #[error("Integer overflow")] IntegerOverflow, #[error("missing geometry column in feature table")] MissingGeometry, #[error("missing string stream: {0}")] MissingStringStream(&'static str), #[error("multiple geometry columns found (only one allowed)")] MultipleGeometryColumns, #[error("multiple ID columns found (only one allowed)")] MultipleIdColumns, #[error("varint uses more bytes than necessary (non-canonical encoding)")] NonCanonicalVarInt, #[error("{0} is not decoded")] NotDecoded(&'static str), #[error("decoded data is not in encoded form")] NotEncoded, #[error("error parsing column type: code={0}")] ParsingColumnType(u8), #[error("error parsing logical technique: code={0}")] ParsingLogicalTechnique(u8), #[error("error parsing physical encoding: code={0}")] ParsingPhysicalEncoding(u8), #[error("error parsing stream type: code={0}")] ParsingStreamType(u8), #[error("found {0} bytes after the expected end of layer")] TrailingLayerData(usize), #[error("unexpected end of input (unable to take {0} bytes)")] UnableToTake(u32), #[error("unexpected stream type {0:?}")] UnexpectedStreamType(StreamType), #[error("unexpected stream type {0:?}, expected {1} for {2}")] UnexpectedStreamType2(StreamType, &'static str, &'static str), #[error("unsupported logical encoding {0:?} for {1}")] UnsupportedLogicalEncoding(LogicalEncoding, &'static str), #[error("invalid combination of logical encodings: {0:?} + {1:?}")] InvalidLogicalEncodings(LogicalTechnique, LogicalTechnique), #[error("layer has zero size")] ZeroLayerSize, #[error("The encoder used to optimise data is incompatible")] BadEncoderDataCombination, #[error("StagedLayer::encode_explicit requires Encoder.explicit to be Some(_)")] MissingExplicitEncoder, // Wire/codec decoding (bytes → primitives) #[error("buffer underflow: needed {0} bytes, but only {1} remain")] BufferUnderflow(u32, usize), #[error("FastPFor decode failed: expected={0} got={1}")] FastPforDecode(u32, usize), #[error("invalid RLE run length (cannot convert to usize): value={0}")] RleRunLenInvalid(i128), // Structural constraints (lengths, counts, shapes) #[error("geometry requires at least 1 stream, got 0")] GeometryWithoutStreams, #[error("FastPFor data byte length expected multiple of 4, got {0}")] InvalidFastPforByteLength(usize), #[error("vec2 delta stream size expected to be non-empty and multiple of 2, got {0}")] InvalidPairStreamSize(usize), #[error("decodable stream size expected {1}, got {0}")] InvalidDecodingStreamSize(usize, usize), #[error("IDs missing for encoding (expected Some IDs, got None)")] IdsMissingForEncoding, #[error("missing struct encoder for struct")] MissingStructEncoderForStruct, #[error("previous decode/parsing attempt failed")] PriorParseFailure, #[error("presence stream has {0} bits set but {1} values provided")] PresenceValueCountMismatch(usize, usize), #[error("MVT parse error: {0}")] MvtParse(String), #[error("need to encode before being able to write")] NeedsEncodingBeforeWriting, #[error("memory limit exceeded: limit={limit}, used={used}, requested={requested}")] MemoryLimitExceeded { limit: u32, used: u32, requested: u32, }, #[error("not implemented: {0}")] NotImplemented(&'static str), #[error("unsupported property value and encoder combination: {0:?} + {1:?}")] UnsupportedPropertyEncoderCombination(&'static str, &'static str), #[error("mixed property types are not allowed in column {0} ({1})")] MixedPropertyTypes(usize, String), #[error("shared dictionary requires at least 2 streams, got {0}")] SharedDictRequiresStreams(usize), #[error("unsupported string stream count (expected between 2 and 5): {0}")] UnsupportedStringStreamCount(usize), #[error("Structs are not allowed to be optional")] TriedToEncodeOptionalStruct, #[error( "encoding instruction count mismatch: expected {input_len} instructions for {input_len} properties, got {config_len}" )] EncodingInstructionCountMismatch { input_len: usize, config_len: usize }, #[error("struct child data streams expected exactly 1 value, got {0}")] UnexpectedStructChildCount(u32), // Note that {expected}+1 is allowed for the legacy Java encoder bug #[error("SharedDict stream count is {actual}, expected {expected}")] InvalidSharedDictStreamCount { actual: u32, expected: u32 }, #[error("unsupported physical encoding: {0}")] UnsupportedPhysicalEncoding(&'static str), #[error("unsupported physical encoding: {0:?} for {1}")] UnsupportedPhysicalEncodingForType(PhysicalEncoding, &'static str), #[error( "Extent {extent} cannot be encoded to morton due to morton allowing max. 16 bits, but {required_bits} would be required" )] VertexMortonNotCompatibleWithExtent { extent: u32, required_bits: u32 }, #[error("Morton stream uses {0} bits, but at most 16 bits are supported")] InvalidMortonBits(u32), // Geometry decode errors (field = variable name, geom_type for context) #[error("MVT error: {0}")] BadMvtGeometry(&'static str), #[error("geometry[{0}]: index out of bounds")] GeometryIndexOutOfBounds(usize), #[error("geometry[{index}]: {field}[{idx}] out of bounds (len={len})")] GeometryOutOfBounds { index: usize, field: &'static str, idx: usize, len: usize, }, #[error("geometry[{index}]: vertex {vertex} out of bounds (count={count})")] GeometryVertexOutOfBounds { index: usize, vertex: usize, count: usize, }, #[error("geometry[{0}]: {1} requires geometry_offsets")] NoGeometryOffsets(usize, GeometryType), #[error("geometry[{0}]: {1} requires part_offsets")] NoPartOffsets(usize, GeometryType), #[error("geometry[{0}]: {1} requires ring_offsets")] NoRingOffsets(usize, GeometryType), #[error("geometry[{0}]: unexpected offset combination for {1}")] UnexpectedOffsetCombination(usize, GeometryType), #[error("FastPFor error: {0}")] FastPfor(#[from] fastpfor::FastPForError), #[error(transparent)] Io(#[from] std::io::Error), #[error("Serde JSON error: {0}")] SerdeJsonError(#[from] serde_json::Error), #[error("integer conversion error: {0}")] TryFromIntError(#[from] TryFromIntError), #[error("num_enum conversion error: {0}")] TryFromPrimitive(#[from] TryFromPrimitiveError), #[error("UTF-8 decode error: {0}")] Utf8(#[from] std::str::Utf8Error), #[error("UTF-8 decode error: {0}")] FromUtf8(#[from] std::string::FromUtf8Error), } impl From for MltError { fn from(_: Infallible) -> Self { unreachable!() } } impl From for std::io::Error { fn from(value: MltError) -> Self { match value { MltError::Io(e) => e, other => Self::other(other), } } } pub(crate) trait AsMltError { fn or_overflow(&self) -> MltResult; } impl AsMltError for Option { #[inline] fn or_overflow(&self) -> MltResult { self.ok_or(MltError::IntegerOverflow) } } impl AsMltError for Result { #[inline] fn or_overflow(&self) -> MltResult { self.map_err(|_| MltError::IntegerOverflow) } } #[inline] pub(crate) fn fail_if_invalid_stream_size(actual: T, expected: T) -> MltResult<()> { if actual == expected { Ok(()) } else { Err(MltError::InvalidDecodingStreamSize( actual.as_usize(), expected.as_usize(), )) } } ================================================ FILE: rust/mlt-core/src/lib.rs ================================================ #![doc = include_str!("../README.md")] extern crate core; /// Validates stream metadata in constructors (crate-internal). macro_rules! validate_stream { ($stream:expr, $expected:pat $(,)?) => { if !matches!($stream.meta.stream_type, $expected) { return Err($crate::MltError::UnexpectedStreamType2( $stream.meta.stream_type, stringify!($expected), stringify!($stream), )); } }; } // re-export geo types pub use geo_types; pub(crate) mod codecs; pub(crate) mod convert; pub(crate) mod decoder; pub mod encoder; pub(crate) mod errors; pub(crate) mod utils; pub use convert::{geojson, mvt}; pub use decoder::{ ColumnRef, Decoder, FeatureRef, GeometryType, GeometryValues, Layer, Layer01, Layer01FeatureIter, LendingIterator, ParsedLayer, ParsedLayer01, Parser, PropName, PropValue, PropValueRef, TileFeature, TileLayer, Unknown, }; // Crate-internal re-exports: allow internal modules to use `crate::Lazy` etc. // without exposing these implementation details to external users. pub(crate) use decoder::{ ColumnType, DictRange, DictionaryType, LengthType, OffsetType, RawPresence, RawSharedDict, RawSharedDictItem, StreamType, }; pub(crate) use errors::MltRefResult; pub use errors::{MltError, MltResult}; pub(crate) use utils::analyze::{Analyze, StatType}; pub(crate) use utils::lazy_state::{Decode, DecodeState, Lazy, LazyParsed, Parsed}; /// Wire-level encoding metadata — for tile analysis and tooling. /// /// These types describe the physical and logical encoding of streams inside an /// MLT tile. Normal tile consumers (parse → iterate features) do not need this /// module; it is intended for tools that inspect or report encoding statistics. pub mod wire { pub use crate::decoder::ColumnType; pub use crate::decoder::stream::model::{ DictionaryType, IntEncoding, LengthType, LogicalEncoding, LogicalTechnique, Morton, OffsetType, PhysicalEncoding, RleMeta, StreamMeta, StreamType, }; pub use crate::utils::analyze::{Analyze, StatType}; } #[cfg(any(test, feature = "__private"))] pub use crate::utils::test_helpers; /// Private re-exports for benchmarks and integration tests. Not part of the public API. #[cfg(any(test, feature = "__private"))] #[doc(hidden)] pub mod __private { pub use crate::codecs::*; pub use crate::convert::*; pub use crate::decoder::*; pub use crate::errors::*; pub use crate::test_helpers::*; pub use crate::utils::analyze::*; pub use crate::utils::lazy_state::*; pub use crate::utils::*; } ================================================ FILE: rust/mlt-core/src/utils/analyze.rs ================================================ use std::ops::Deref; use enum_dispatch::enum_dispatch; use crate::LazyParsed; use crate::decoder::{ParsedProperty, ParsedScalar, ParsedSharedDict, ParsedStrings, StreamMeta}; /// What to calculate with [`Analyze::collect_statistic`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum StatType { /// Geometry/Feature/id data size in bytes (excludes metadata overhead). DecodedDataSize, /// Metadata overhead in bytes (stream headers, names, extent, geometry types). DecodedMetaSize, /// Number of features (geometry entries). FeatureCount, } /// Trait for estimating various size/count metrics. #[enum_dispatch] pub trait Analyze { fn collect_statistic(&self, _stat: StatType) -> usize { 0 } /// Call `cb` with the [`StreamMeta`] of every stream contained in `self`. /// Default implementation is a no-op (types that hold no streams). fn for_each_stream(&self, _cb: &mut dyn FnMut(StreamMeta)) {} } macro_rules! impl_statistics_fixed { ($($ty:ty),+) => { $(impl Analyze for $ty { fn collect_statistic(&self, _stat: StatType) -> usize { size_of::<$ty>() } } impl Analyze for &[$ty] { fn collect_statistic(&self, _stat: StatType) -> usize { size_of::<$ty>() * self.len() } })+ }; } impl_statistics_fixed!(bool, i8, u8, i16, u16, i32, u32, i64, u64, f32, f64); impl Analyze for String { fn collect_statistic(&self, _stat: StatType) -> usize { self.len() } } impl Analyze for Option { fn collect_statistic(&self, stat: StatType) -> usize { self.as_ref().map_or(0, |v| v.collect_statistic(stat)) } fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { if let Some(v) = self { v.for_each_stream(cb); } } } impl Analyze for [T] { fn collect_statistic(&self, stat: StatType) -> usize { self.iter().map(|v| v.collect_statistic(stat)).sum() } fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { for v in self { v.for_each_stream(cb); } } } impl Analyze for Vec { fn collect_statistic(&self, stat: StatType) -> usize { self.as_slice().collect_statistic(stat) } fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { self.as_slice().for_each_stream(cb); } } /// Opt-in marker for blanket `Analyze` delegation via `Deref`. /// /// A type that implements both `Deref` (with `T: Analyze`) and this /// marker trait automatically receives an `Analyze` impl that delegates every call /// to the dereferenced value. pub(crate) trait AnalyzeViaDeref {} impl + AnalyzeViaDeref> Analyze for R { fn collect_statistic(&self, stat: StatType) -> usize { (**self).collect_statistic(stat) } fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { (**self).for_each_stream(cb); } } impl Analyze for LazyParsed { fn collect_statistic(&self, stat: StatType) -> usize { match self { Self::Raw(encoded) => encoded.collect_statistic(stat), Self::Parsed(decoded) => decoded.collect_statistic(stat), Self::ParsingFailed => 0, } } fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) { match self { Self::Raw(encoded) => encoded.for_each_stream(cb), Self::Parsed(decoded) => decoded.for_each_stream(cb), Self::ParsingFailed => {} } } } ================================================ FILE: rust/mlt-core/src/utils/extensions.rs ================================================ use num_traits::CheckedAdd; use crate::errors::AsMltError as _; use crate::{MltError, MltResult}; pub trait SetOptionOnce { fn set_once(&mut self, value: T) -> MltResult<()>; } impl SetOptionOnce for Option { fn set_once(&mut self, value: T) -> MltResult<()> { if self.replace(value).is_some() { Err(MltError::DuplicateValue) } else { Ok(()) } } } pub trait AsUsize: Eq + Copy { fn as_usize(&self) -> usize; } impl AsUsize for usize { #[inline] fn as_usize(&self) -> usize { *self } } impl AsUsize for u32 { #[inline] fn as_usize(&self) -> usize { const _: () = { // Some day Rust may support usize smaller than u32? assert!( size_of::() <= size_of::(), "usize must be able to hold all u32 values" ); }; usize::try_from(*self).unwrap() } } /// Perform checked addition of two values, returning an error if any overflow occurs. #[inline] pub fn checked_sum2(v1: T, v2: T) -> MltResult { v1.checked_add(&v2).or_overflow() } /// Perform checked addition of three values, returning an error if any overflow occurs. #[inline] pub fn checked_sum3(v1: T, v2: T, v3: T) -> MltResult { v1.checked_add(&v2) .and_then(|sum| sum.checked_add(&v3)) .or_overflow() } ================================================ FILE: rust/mlt-core/src/utils/formatter.rs ================================================ use std::fmt::{self, Debug, Display, Formatter}; use hex::ToHex as _; fn format_byte_array(data: &[u8]) -> String { let vals = data.encode_hex_upper::(); format!("[0x{vals}; {}]", data.len()) } /// derive-debug formatter for `&[u8]` fields. pub fn bytes_dbg(data: &&[u8]) -> String { format_byte_array(data) } fn format_opt_seq(v: Option<&[T]>) -> String { match v { None => "None".to_string(), Some(v) => { let items = v .iter() .map(ToString::to_string) .collect::>() .join(","); format!("[{items}; {}]", v.len()) } } } /// derive-debug formatter for `Vec` fields. pub fn vec_seq(v: &[T]) -> String { format_opt_seq(Some(v)) } /// derive-debug formatter for `Option>` fields. #[allow(clippy::ref_option, reason = "called by Dbg codegen")] pub fn opt_vec_seq(v: &Option>) -> String { format_opt_seq(v.as_deref()) } /// Wraps any `Debug` value and formats it in compact (non-alternate) mode. /// Used to prevent inner types from expanding to multiple lines in `{:#?}` output. pub struct CompactDbg(T); impl Display for CompactDbg { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.0) } } /// derive-debug formatter that forces compact `{:?}` output regardless of alternate mode. pub fn compact_dbg(t: &T) -> CompactDbg<&T> { CompactDbg(t) } ================================================ FILE: rust/mlt-core/src/utils/lazy_state.rs ================================================ use std::mem; use crate::{Decoder, MltError, MltResult}; pub trait Decode: Sized { fn decode(self, decoder: &mut Decoder) -> MltResult; } mod sealed { pub trait Sealed {} } /// Type-state marker for [`Layer01`](crate::Layer01) and related column wrappers. /// /// Implementors determine how `(Raw, Parsed)` column pairs are stored: /// - [`Lazy`] stores an [`LazyParsed`] enum that can be in `Raw`, `Parsed`, or `ParsingFailed` state. /// - [`Parsed`] stores only `Parsed`, giving zero-cost infallible field access. pub trait DecodeState: sealed::Sealed { type LazyOrParsed; } /// Lazy state: individual columns may still be raw or already decoded. /// /// This is the default state returned by [`Parser::parse_layers`](crate::Parser::parse_layers). /// All columns can be decoded at once by calling [`Layer01::decode_all`](crate::Layer01::decode_all), /// which consumes `self` and returns a [`Layer01`](crate::Layer01). #[derive(Debug, Clone, PartialEq)] pub struct Lazy; /// Fully-decoded state: all columns hold their parsed values directly. /// /// A `Layer01` is produced by [`Layer01::decode_all`](crate::Layer01::decode_all). /// Its fields (`id`, `geometry`, `properties`) are the parsed types themselves — no /// wrapping enum, no `Result`, just plain field access. #[derive(Debug, Clone, PartialEq)] pub struct Parsed; impl sealed::Sealed for Lazy {} impl sealed::Sealed for Parsed {} impl DecodeState for Lazy { type LazyOrParsed = LazyParsed; } impl DecodeState for Parsed { /// In the decoded state the column IS the parsed value — no enum wrapper. type LazyOrParsed = Parsed; } /// Shared wrapper for values that may still be in the original (raw) format or /// already parsed (but still columnar). /// Used by: `Id`, `Geometry`, `Property`, and eventually - `SharedDictItem` #[allow(clippy::large_enum_variant)] #[derive(Debug, PartialEq, Clone)] #[cfg_attr(all(not(test), feature = "arbitrary"), derive(arbitrary::Arbitrary))] pub enum LazyParsed { Raw(Raw), Parsed(Parsed), ParsingFailed, } impl, Parsed> LazyParsed { /// Decode in place, replacing the raw value with the parsed result. pub fn decode(&mut self, decoder: &mut Decoder) -> MltResult<&mut Parsed> { match self { Self::Parsed(v) => Ok(v), Self::Raw(_) => { let Self::Raw(raw) = mem::replace(self, Self::ParsingFailed) else { unreachable!(); }; *self = Self::Parsed(raw.decode(decoder)?); let Self::Parsed(v) = self else { unreachable!() }; Ok(v) } Self::ParsingFailed => Err(MltError::PriorParseFailure), } } /// Consume and return the parsed value, decoding if currently raw. pub fn into_parsed(self, decoder: &mut Decoder) -> MltResult { match self { Self::Parsed(v) => Ok(v), Self::Raw(raw) => raw.decode(decoder), Self::ParsingFailed => Err(MltError::PriorParseFailure), } } pub fn as_parsed(&self) -> MltResult<&Parsed> { match self { Self::Parsed(v) => Ok(v), Self::Raw(_) => Err(MltError::NotDecoded("enc_dec value")), // TODO: I wonder if the str can be of the type name? Self::ParsingFailed => Err(MltError::PriorParseFailure), } } } ================================================ FILE: rust/mlt-core/src/utils/mod.rs ================================================ pub(crate) mod analyze; pub(crate) mod extensions; pub(crate) mod formatter; pub(crate) mod lazy_state; mod parse; pub mod presence; mod serialize; #[cfg(any(test, feature = "__private"))] pub mod test_helpers; pub use extensions::*; pub(crate) use parse::*; pub use presence::*; pub use serialize::*; ================================================ FILE: rust/mlt-core/src/utils/parse.rs ================================================ use crate::codecs::varint::parse_varint; use crate::utils::Presence; use crate::{Decoder, MltError, MltRefResult, MltResult, RawPresence}; #[inline] pub fn take(input: &[u8], size: u32) -> MltRefResult<'_, &[u8]> { let (value, input) = input .split_at_checked(size.try_into()?) .ok_or(MltError::UnableToTake(size))?; Ok((input, value)) } /// Parse a length-prefixed UTF-8 string from the input pub fn parse_string(input: &[u8]) -> MltRefResult<'_, &str> { let (input, length) = parse_varint::(input)?; let (input, value) = take(input, length)?; let value = str::from_utf8(value)?; Ok((input, value)) } /// Parse a single byte from the input pub fn parse_u8(input: &[u8]) -> MltRefResult<'_, u8> { if input.is_empty() { Err(MltError::UnableToTake(1)) } else { Ok((&input[1..], input[0])) } } /// Decode an optional presence stream, combining it with the dense values. /// /// Returns [`Presence::AllPresent`] wrapping `values` when `presence.0` is `None` /// (non-optional column). Otherwise decodes the bitvector and checks that the /// number of set bits equals `values.len()` (the number of non-null values already decoded). pub fn decode_presence<'a, T: Copy>( presence: RawPresence<'a>, values: Vec, dec: &mut Decoder, ) -> MltResult> { let Some(raw) = presence.0 else { return Ok(Presence::AllPresent(values)); }; let bits = raw.decode_bitvec(dec)?; let set_count = bits.count_ones(); let dense_count = values.len(); if set_count != dense_count { return Err(MltError::PresenceValueCountMismatch(set_count, dense_count)); } Ok(Presence::Bits { bits, values }) } ================================================ FILE: rust/mlt-core/src/utils/presence.rs ================================================ use std::borrow::Cow; use bitvec::order::Lsb0; use bitvec::slice::BitSlice; use crate::{Analyze, StatType}; /// Per-column feature presence bitvector paired with its dense values. /// /// Bit order matches the wire format (`bitvec`'s `Lsb0`): bit `i` corresponds to /// `(byte[i/8] >> (i%8)) & 1`. #[derive(Clone, PartialEq, Debug)] pub enum Presence<'a, T: Copy> { /// No presence stream — every feature has a value. AllPresent(Vec), /// Per-feature packed bitvector: bit `i` is set iff feature `i` has a value. /// `values` holds only the non-null (present) entries in dense order. Bits { bits: Cow<'a, BitSlice>, values: Vec, }, } impl Presence<'_, T> { /// Returns `true` if feature `idx` is present, `false` if absent or out of bounds. #[inline] #[must_use] pub fn is_present(&self, idx: usize) -> bool { match self { Self::AllPresent(values) => idx < values.len(), Self::Bits { bits, .. } => bits.get(idx).as_deref().copied().unwrap_or(false), } } /// Total number of features (present and absent). #[inline] #[must_use] pub fn feature_count(&self) -> usize { match self { Self::AllPresent(values) => values.len(), Self::Bits { bits, .. } => bits.len(), } } /// Dense values slice (present entries only). #[inline] #[must_use] pub fn dense_values(&self) -> &[T] { match self { Self::AllPresent(values) | Self::Bits { values, .. } => values, } } /// Returns the value for feature `idx`, or `None` if absent or out of bounds. /// /// For sequential access over all features prefer [`Presence::iter_optional`], /// which is O(1) per step. This method recomputes `count_ones()` each call and /// is O(idx) for sparse (Bits) presence. #[inline] #[must_use] pub fn get(&self, idx: usize) -> Option { match self { Self::AllPresent(values) => values.get(idx).copied(), Self::Bits { bits, values } => { if *bits.get(idx)? { Some(values[bits[..idx].count_ones()]) } else { None } } } } /// Expand into a `Vec>` with one entry per feature. /// /// Allocates; prefer [`Presence::get`] for single-feature access or /// [`Presence::iter_optional`] for sequential access without allocation. #[must_use] pub fn materialize(&self) -> Vec> { self.iter_optional().collect() } /// Iterate over all features in order, yielding `Option` per feature in O(1) per step. /// /// Unlike repeated [`Presence::get`] calls (which are O(idx) for sparse columns), /// this iterator tracks `dense_idx` internally and advances in O(1) per step. #[must_use] pub fn iter_optional(&self) -> PresenceOptIter<'_, T> { match self { Self::AllPresent(values) => PresenceOptIter { bits: None, values, feat_idx: 0, dense_idx: 0, }, Self::Bits { bits, values } => PresenceOptIter { bits: Some(bits), values, feat_idx: 0, dense_idx: 0, }, } } } impl Analyze for Presence<'_, T> { fn collect_statistic(&self, stat: StatType) -> usize { if stat == StatType::DecodedMetaSize { 0 } else { let bits_size = match self { Self::AllPresent(_) => 0, Self::Bits { bits, .. } => bits.len().div_ceil(8), }; bits_size + self.dense_values().collect_statistic(stat) } } } /// O(1)-per-step iterator over all features of a [`Presence`], yielding `Option`. /// /// Returned by [`Presence::iter_optional`]. Prefer this over repeated [`Presence::get`] /// calls when iterating in order: `get` is O(idx) for sparse columns (recomputes /// `count_ones()`), while this iterator advances in O(1) per step by tracking /// `dense_idx` internally. pub struct PresenceOptIter<'p, T: Copy> { /// `None` for `AllPresent`, `Some(bits)` for `Bits`. bits: Option<&'p BitSlice>, values: &'p [T], feat_idx: usize, dense_idx: usize, } impl Iterator for PresenceOptIter<'_, T> { type Item = Option; fn next(&mut self) -> Option> { match self.bits { None => { let v = self.values.get(self.feat_idx).copied()?; self.feat_idx += 1; Some(Some(v)) } Some(bits) => { if self.feat_idx >= bits.len() { return None; } let present = bits[self.feat_idx]; self.feat_idx += 1; if present { let v = self.values[self.dense_idx]; self.dense_idx += 1; Some(Some(v)) } else { Some(None) } } } } fn size_hint(&self) -> (usize, Option) { let remaining = match self.bits { None => self.values.len().saturating_sub(self.feat_idx), Some(bits) => bits.len().saturating_sub(self.feat_idx), }; (remaining, Some(remaining)) } } impl ExactSizeIterator for PresenceOptIter<'_, T> {} ================================================ FILE: rust/mlt-core/src/utils/serialize.rs ================================================ use std::io; use std::io::Write; use integer_encoding::VarIntWriter; #[cfg(any(test, feature = "__private"))] use crate::encoder::EncodedStream; use crate::{MltError, MltResult}; pub trait BinarySerializer: Write + VarIntWriter + Sized { fn write_u8(&mut self, value: u8) -> io::Result<()> { self.write_all(&[value]) } fn write_string(&mut self, value: &str) -> io::Result<()> { let size = u32::try_from(value.len()).map_err(MltError::from)?; self.write_varint(size)?; self.write_all(value.as_bytes()) } /// Reverses `RawStream::from_bytes` — writes a stream header then the stream data bytes. #[cfg(any(test, feature = "__private"))] fn write_stream(&mut self, stream: &EncodedStream) -> io::Result<()> { let byte_length = u32::try_from(stream.data.len()).map_err(MltError::from)?; stream.meta.write_to(self, false, byte_length)?; stream.write_to(self)?; Ok(()) } /// Reverses `RawStream::parse_bool` — writes a boolean stream header then the stream data bytes. #[cfg(test)] fn write_boolean_stream(&mut self, stream: &EncodedStream) -> io::Result<()> { let byte_length = u32::try_from(stream.data.len()).map_err(MltError::from)?; stream.meta.write_to(self, true, byte_length)?; stream.write_to(self)?; Ok(()) } } impl BinarySerializer for T where T: Write + VarIntWriter {} pub fn strings_to_lengths>(values: &[S]) -> MltResult> { Ok(values .iter() .map(|s| u32::try_from(s.as_ref().len())) .collect::, _>>()?) } ================================================ FILE: rust/mlt-core/src/utils/test_helpers.rs ================================================ //! Shared helpers for unit tests, integration tests, and benchmarks. use crate::decoder::Layer01; use crate::{Decoder, Layer, MltRefResult, Parser}; /// Default decoder for decoding in tests. #[must_use] pub fn dec() -> Decoder { Decoder::default() } /// Default parser for parsing in tests. #[must_use] pub fn parser() -> Parser { Parser::default() } pub fn assert_empty(result: MltRefResult) -> T { let (remaining, value) = result.unwrap(); assert!(remaining.is_empty(), "{} bytes remain", remaining.len()); value } #[must_use] pub fn into_layer01(layer: Layer) -> Layer01 { match layer { Layer::Tag01(layer01) => layer01, Layer::Unknown(_) => panic!("expected Tag01 layer"), } } ================================================ FILE: rust/mlt-core/tests/geojson.rs ================================================ use std::fs; use std::path::Path; use mlt_core::geojson::FeatureCollection; use mlt_core::test_helpers::{dec, parser}; use test_each_file::test_each_path; test_each_path! { for ["mlt"] in "../test/expected/tag0x01" as geojson => geojson_test } fn geojson_test([mlt]: [&Path; 1]) { let buffer = fs::read(mlt).unwrap(); let mut p = parser(); let layers = p.parse_layers(&buffer).unwrap(); assert!(p.reserved() > 0); let mut d = dec(); let decoded = d.decode_all(layers).unwrap(); assert!(d.consumed() > 0); let fc = FeatureCollection::from_layers(decoded).unwrap(); assert!(!fc.features.is_empty(), "expected at least one feature"); } ================================================ FILE: rust/mlt-core/tests/snapshots.rs ================================================ #![allow(unused)] use std::ffi::OsString; use std::fs; use std::path::Path; use insta::{assert_debug_snapshot, assert_snapshot, with_settings}; use mlt_core::test_helpers::{dec, parser}; use test_each_file::test_each_path; // // ATTENTION: this test is likely to be deleted soon. // Geometry validation is done via `mlt ls --validate-to-json`. // See the CLI integration tests / CI configuration for details. // test_each_path! { for ["mlt"] in "../test/expected/tag0x01" as parse => parse } fn parse([path]: [&Path; 1]) { let mut snapshot_path = OsString::from("snapshots-"); snapshot_path.push(path.parent().unwrap().file_name().unwrap()); with_settings! { { omit_expression => true, snapshot_path => snapshot_path , prepend_module_to_snapshot => false }, { parse_one_file(path); } } } /// Parse a single MLT file and assert a snapshot of the result. fn parse_one_file(path: impl AsRef) { let path = path.as_ref(); eprintln!("Parsing MLT file: {}", path.display()); let file_name = path.file_stem().unwrap().to_string_lossy().to_string(); let buffer = fs::read(path).unwrap(); let mut p = parser(); let mut d = dec(); match p.parse_layers(&buffer) { Ok(layers) => { assert_snapshot!(p.reserved(), @""); assert_debug_snapshot!(format!("{file_name}-parsed"), layers); let mut decoded = Vec::with_capacity(layers.len()); for (idx, layer) in layers.into_iter().enumerate() { match layer.decode_all(&mut d) { Ok(l) => decoded.push(l), Err(e) => { let idx = if idx == 0 { String::new() } else { format!("_{idx}") }; assert_debug_snapshot!(format!("{file_name}{idx}___bad-decode"), e); break; } } } assert_snapshot!(d.consumed(), @""); assert_debug_snapshot!(format!("{file_name}-decoded"), decoded); } Err(e) => { let filesize = buffer.len(); assert_debug_snapshot!(format!("{file_name}___bad___{filesize}"), e); } } } // test_each_path! { for ["mlt"] in "../test/expected/tag0x01" as decode => decode } // fn decode([path]: [&Path; 1]) { // let buffer = fs::read(path).unwrap(); // let mut layers = parse_layers(&buffer).expect("MLT file parse"); // for layer in &mut layers { // match layer.decode_all() { // Ok(v) => { // assert_debug_snapshot!(path.as_str(), v); // } // Err(e) => { // // assert_debug_snapshot!(format!("{file_name}___bad-decode"), _e); // todo!("handle decode error: {e:#?}"); // } // } // } // } #[test] #[ignore = "used for manual testing of a single file"] fn test_plain() { // let path = "../../test/expected/tag0x01/simple/line-boolean.mlt"; let path = "../../test/expected/tag0x01/omt/11_1062_1368.mlt"; // let path = "../../test/expected/tag0x01/omt/11_1062_1368.mlt"; // let path = "../../test/expected/tag0x01/bing/6-32-21.mlt"; let path = Path::new(path); let buffer = fs::read(path).unwrap(); let mut p = parser(); let layers = p.parse_layers(&buffer).unwrap(); assert_snapshot!(p.reserved(), @""); let _ = layers; // decode([&path]); } ================================================ FILE: rust/mlt-core/tests/unknown_layer.rs ================================================ /// Integration tests for [`mlt_core::Unknown`] public accessors. /// /// Builds a minimal hand-crafted byte buffer that contains one unknown layer /// (tag = 42, body = [1, 2, 3]) and verifies that `Parser::parse_layers` yields /// a `Layer::Unknown` whose `tag()` and `data()` return the expected values. /// /// Wire format of a single layer: /// ```text /// [varint: body_len_including_tag_byte] [tag_byte] [body_bytes...] /// /// For tag=42, body=[1,2,3]: /// body_len = 1 (tag byte) + 3 (body bytes) = 4 /// bytes = [0x04, 0x2A, 0x01, 0x02, 0x03] /// ``` use mlt_core::{Layer, Parser}; fn unknown_layer_bytes(tag: u8, body: &[u8]) -> Vec { // size varint = 1 (tag byte) + body.len() let size = (1 + body.len()) as u64; let mut buf = Vec::new(); // encode size as varint let mut v = size; loop { let byte = (v & 0x7F) as u8; v >>= 7; if v == 0 { buf.push(byte); break; } buf.push(byte | 0x80); } buf.push(tag); buf.extend_from_slice(body); buf } #[test] fn unknown_tag_and_data_are_accessible() { let body: &[u8] = &[1, 2, 3]; let raw = unknown_layer_bytes(42, body); let layers = Parser::default() .parse_layers(&raw) .expect("parse should succeed"); assert_eq!(layers.len(), 1); let Layer::Unknown(u) = &layers[0] else { panic!("expected Layer::Unknown, got {:?}", layers[0]); }; assert_eq!(u.tag(), 42u32); assert_eq!(u.data(), body); } #[test] fn unknown_zero_length_body() { let raw = unknown_layer_bytes(99, &[]); let layers = Parser::default() .parse_layers(&raw) .expect("parse should succeed"); let Layer::Unknown(u) = &layers[0] else { panic!("expected Layer::Unknown"); }; assert_eq!(u.tag(), 99u32); assert!(u.data().is_empty()); } #[test] fn multiple_layers_mixed_unknown_and_tag01() { // Build two unknown layers back-to-back (tags 2 and 3, since tag=1 is Tag01). let mut raw = unknown_layer_bytes(2, b"hello"); raw.extend_from_slice(&unknown_layer_bytes(3, b"world")); let layers = Parser::default() .parse_layers(&raw) .expect("parse should succeed"); assert_eq!(layers.len(), 2); let Layer::Unknown(u0) = &layers[0] else { panic!("expected Unknown at index 0"); }; assert_eq!(u0.tag(), 2u32); assert_eq!(u0.data(), b"hello"); let Layer::Unknown(u1) = &layers[1] else { panic!("expected Unknown at index 1"); }; assert_eq!(u1.tag(), 3u32); assert_eq!(u1.data(), b"world"); } ================================================ FILE: rust/mlt-py/CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [0.1.12](https://github.com/maplibre/maplibre-tile-spec/compare/python-mlt-v0.1.11...python-mlt-v0.1.12) - 2026-04-29 ### Other - *(rust)* follow up to Presence cleanup ([#1337](https://github.com/maplibre/maplibre-tile-spec/pull/1337)) ## [0.1.11](https://github.com/maplibre/maplibre-tile-spec/compare/python-mlt-v0.1.10...python-mlt-v0.1.11) - 2026-04-18 ### Other - *(rust)* Coord32 cleanup, dep update ([#1304](https://github.com/maplibre/maplibre-tile-spec/pull/1304)) ## [0.1.10](https://github.com/maplibre/maplibre-tile-spec/compare/python-mlt-v0.1.9...python-mlt-v0.1.10) - 2026-04-13 ### Other - update Cargo.toml dependencies ## [0.1.9](https://github.com/maplibre/maplibre-tile-spec/compare/python-mlt-v0.1.8...python-mlt-v0.1.9) - 2026-04-13 ### Added - *(rust)* enhance property iteration ([#1225](https://github.com/maplibre/maplibre-tile-spec/pull/1225)) ### Other - *(rust)* move frames/v01 -> decoder, adj use ([#1246](https://github.com/maplibre/maplibre-tile-spec/pull/1246)) - *(rust)* mv tessellation to core ([#1220](https://github.com/maplibre/maplibre-tile-spec/pull/1220)) - *(rust)* implement feature/property iterator and more type state ([#1198](https://github.com/maplibre/maplibre-tile-spec/pull/1198)) - *(rust)* type state to represent fully-decoded layers ([#1171](https://github.com/maplibre/maplibre-tile-spec/pull/1171)) - *(rust)* cleanup Results, minor styling ([#1192](https://github.com/maplibre/maplibre-tile-spec/pull/1192)) ## [0.1.8](https://github.com/maplibre/maplibre-tile-spec/compare/python-mlt-v0.1.7...python-mlt-v0.1.8) - 2026-03-23 ### Other - *(rust)* migrate to Rust fastpfor ([#1190](https://github.com/maplibre/maplibre-tile-spec/pull/1190)) ## [0.1.7](https://github.com/maplibre/maplibre-tile-spec/compare/python-mlt-v0.1.6...python-mlt-v0.1.7) - 2026-03-17 ### Other - *(rust)* memory budgeting, codecs ([#1168](https://github.com/maplibre/maplibre-tile-spec/pull/1168)) - *(rust)* introduce EncDec decode states ([#1166](https://github.com/maplibre/maplibre-tile-spec/pull/1166)) - *(rust)* rename EncDec variants ([#1165](https://github.com/maplibre/maplibre-tile-spec/pull/1165)) - *(rust)* add stateful decoder ([#1163](https://github.com/maplibre/maplibre-tile-spec/pull/1163)) - *(rust)* rename to IdValues and GeometryValues ([#1159](https://github.com/maplibre/maplibre-tile-spec/pull/1159)) - *(chore)* remove into_static, test fixes ([#1154](https://github.com/maplibre/maplibre-tile-spec/pull/1154)) - *(rust)* refactor parsing and encoding code ([#1144](https://github.com/maplibre/maplibre-tile-spec/pull/1144)) - *(rust)* simplify ID model ([#1139](https://github.com/maplibre/maplibre-tile-spec/pull/1139)) - *(rust)* move name into DecodedStrings ([#1108](https://github.com/maplibre/maplibre-tile-spec/pull/1108)) ## [0.1.6](https://github.com/maplibre/maplibre-tile-spec/compare/python-mlt-v0.1.5...python-mlt-v0.1.6) - 2026-03-10 ### Other - *(rust)* rework our internal data model ([#1099](https://github.com/maplibre/maplibre-tile-spec/pull/1099)) - *(rust)* move all structs to structs.rs ([#1094](https://github.com/maplibre/maplibre-tile-spec/pull/1094)) ## [0.1.5](https://github.com/maplibre/maplibre-tile-spec/compare/python-mlt-v0.1.4...python-mlt-v0.1.5) - 2026-03-08 ### Other - *(rust)* major rework of the shared dict ([#1066](https://github.com/maplibre/maplibre-tile-spec/pull/1066)) ## [0.1.4](https://github.com/maplibre/maplibre-tile-spec/compare/mlt-pyo3-v0.1.3...python-mlt-v0.1.4) - 2026-03-04 ### Other - *(rust)* rework string parsing ([#1026](https://github.com/maplibre/maplibre-tile-spec/pull/1026)) ## [0.1.3](https://github.com/maplibre/maplibre-tile-spec/compare/mlt-pyo3-v0.1.2...mlt-pyo3-v0.1.3) - 2026-02-25 ### Other - *(python)* Pyo3 type stub generation ([#966](https://github.com/maplibre/maplibre-tile-spec/pull/966)) ## [0.1.2](https://github.com/maplibre/maplibre-tile-spec/compare/mlt-pyo3-v0.1.1...mlt-pyo3-v0.1.2) - 2026-02-25 ### Other - updated the following local packages: mlt-core ## [0.1.1](https://github.com/maplibre/maplibre-tile-spec/compare/mlt-pyo3-v0.1.0...mlt-pyo3-v0.1.1) - 2026-02-25 ### Added - *(rust)* add qgis plugin for MLT ([#886](https://github.com/maplibre/maplibre-tile-spec/pull/886)) ================================================ FILE: rust/mlt-py/Cargo.toml ================================================ [package] name = "mlt-py" description = "Python bindings for MapLibre Tile (MLT) format via PyO3" version = "0.1.12" readme = "README.md" repository.workspace = true edition.workspace = true license.workspace = true keywords.workspace = true categories.workspace = true rust-version.workspace = true [lib] name = "mlt_py" crate-type = ["cdylib", "rlib"] [[bin]] name = "stub_gen" path = "src/bins/stub_gen.rs" required-features = ["abi3"] [features] abi3 = ["pyo3/abi3-py310", "generate-import-lib"] generate-import-lib = ["pyo3/generate-import-lib"] [dependencies] mlt-core.workspace = true pyo3.workspace = true pyo3-stub-gen.workspace = true serde_json.workspace = true [dev-dependencies] pyo3 = { workspace = true, features = ["auto-initialize"] } ================================================ FILE: rust/mlt-py/README.md ================================================ # mlt_py Python bindings for the MapLibre Tile (MLT) format via [PyO3](https://pyo3.rs/). ```python import maplibre_tiles data = open("tile.mlt", "rb").read() # Structured decode with geo-referencing layers = maplibre_tiles.decode_mlt(data, z=14, x=8297, y=10749, tms=True) for layer in layers: print(f"Layer: {layer.name}, extent: {layer.extent}") for feat in layer.features[:3]: print(f" id={feat.id}, type={feat.geometry_type}") print(f" wkb={len(feat.wkb)} bytes, props={dict(feat.properties)}") # Raw tile-local coordinates (no z/x/y needed) layers = maplibre_tiles.decode_mlt(data) # GeoJSON string output (tile-local coords) geojson_str = maplibre_tiles.decode_mlt_to_geojson(data) # Fast layer listing (no full decode) names = maplibre_tiles.list_layers(data) ``` ================================================ FILE: rust/mlt-py/maplibre_tiles.pyi ================================================ # This file is automatically generated by pyo3_stub_gen # ruff: noqa: E501, F401, F403, F405 import builtins import typing __all__ = [ "MltFeature", "MltLayer", "decode_mlt", "decode_mlt_to_geojson", "list_layers", ] @typing.final class MltFeature: r""" A decoded MLT feature with geometry, id, and properties. """ @property def id(self) -> typing.Optional[builtins.int]: ... @property def geometry_type(self) -> builtins.str: ... @property def wkb(self) -> bytes: ... @property def properties(self) -> dict: ... def __repr__(self) -> builtins.str: ... @typing.final class MltLayer: r""" A decoded MLT layer containing features. """ @property def name(self) -> builtins.str: ... @property def extent(self) -> builtins.int: ... @property def features(self) -> builtins.list[MltFeature]: ... def __repr__(self) -> builtins.str: ... def decode_mlt(data: bytes, z: typing.Optional[builtins.int] = None, x: typing.Optional[builtins.int] = None, y: typing.Optional[builtins.int] = None, tms: builtins.bool = True) -> builtins.list[MltLayer]: r""" Decode an MLT binary blob into a list of `MltLayer` objects. If `z`, `x`, `y` are provided, tile-local coordinates are transformed to EPSG:3857 (Web Mercator) meters. Without them, raw tile coordinates are preserved. `tms`: when True (the default), treat `y` as TMS convention (y=0 at south, used by OpenMapTiles / MBTiles). Set to False for XYZ / slippy-map tiles (y=0 at north, e.g. OSM raster tiles). """ def decode_mlt_to_geojson(data: bytes) -> builtins.str: r""" Decode an MLT binary blob and return GeoJSON as a string. """ def list_layers(data: bytes) -> builtins.list[builtins.str]: r""" Return a list of layer names without fully decoding. """ ================================================ FILE: rust/mlt-py/pyproject.toml ================================================ [build-system] requires = ["maturin>=1.7,<2.0"] build-backend = "maturin" [project] name = "maplibre-tiles" version = "0.1.12" description = "Python bindings for MapLibre Tile (MLT) format" requires-python = ">=3.10" license = { text = "MIT OR Apache-2.0" } keywords = ["maplibre", "tile", "mlt", "vector-tiles", "gis"] readme = "README.md" classifiers = [ "License :: OSI Approved :: MIT License", "License :: OSI Approved :: Apache Software License", "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Intended Audience :: Science/Research", "Intended Audience :: Information Technology", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Rust", "Operating System :: OS Independent", "Topic :: Scientific/Engineering :: GIS", "Topic :: Multimedia :: Graphics", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Libraries", ] [project.urls] Homepage = "https://maplibre.org/maplibre-tile-spec" Documentation = "https://maplibre.org/maplibre-tile-spec" Repository = "https://github.com/maplibre/maplibre-tile-spec.git" Issues = "https://github.com/maplibre/maplibre-tile-spec/issues" Changelog = "https://github.com/maplibre/maplibre-tile-spec/blob/master/rust/mlt-py/CHANGELOG.md" [tool.maturin] manifest-path = "Cargo.toml" module-name = "maplibre_tiles" ================================================ FILE: rust/mlt-py/src/bins/stub_gen.rs ================================================ use pyo3_stub_gen::Result; /// purely a helper bin to generate the type stubs by CI fn main() -> Result<()> { let stub = mlt_py::stub_info()?; stub.generate()?; Ok(()) } ================================================ FILE: rust/mlt-py/src/feature.rs ================================================ use pyo3::types::{PyBytes, PyDict}; use pyo3::{Py, pyclass, pymethods}; use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; /// A decoded MLT feature with geometry, id, and properties. #[gen_stub_pyclass] #[pyclass] pub struct MltFeature { #[pyo3(get)] id: Option, #[pyo3(get)] geometry_type: String, #[pyo3(get)] wkb: Py, #[pyo3(get)] properties: Py, } #[gen_stub_pymethods] #[pymethods] impl MltFeature { fn __repr__(&self) -> String { format!( "MltFeature(id={:?}, geometry_type={:?})", self.id, self.geometry_type ) } } impl MltFeature { pub(crate) fn new( id: Option, geometry_type: String, wkb: Py, properties: Py, ) -> Self { MltFeature { id, geometry_type, wkb, properties, } } } ================================================ FILE: rust/mlt-py/src/lib.rs ================================================ mod feature; mod tile_transform; use std::iter::once; use std::ops::Deref; use mlt_core::geo_types::{Geometry, LineString, Polygon}; use mlt_core::geojson::FeatureCollection; use mlt_core::{ Decoder, GeometryType, Layer, LendingIterator, MltError, MltResult, ParsedLayer01, Parser, PropValueRef, }; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use pyo3::types::{PyBytes, PyDict}; use pyo3_stub_gen::define_stub_info_gatherer; use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pyfunction, gen_stub_pymethods}; use tile_transform::TileTransform; use crate::feature::MltFeature; fn mlt_err(e: MltError) -> PyErr { PyValueError::new_err(format!("MLT decode error: {e}")) } /// A decoded MLT layer containing features. #[gen_stub_pyclass] #[pyclass] struct MltLayer { #[pyo3(get)] name: String, #[pyo3(get)] extent: u32, #[pyo3(get)] features: Vec>, } #[gen_stub_pymethods] #[pymethods] impl MltLayer { fn __repr__(&self) -> String { format!( "MltLayer(name={:?}, extent={}, features=<{} features>)", self.name, self.extent, self.features.len() ) } } fn push_coord_raw(buf: &mut Vec, coord: [i32; 2]) { buf.extend_from_slice(&f64::from(coord[0]).to_le_bytes()); buf.extend_from_slice(&f64::from(coord[1]).to_le_bytes()); } fn push_coord_xform(buf: &mut Vec, coord: [i32; 2], xf: TileTransform) { let [x, y] = xf.apply(coord); buf.extend_from_slice(&x.to_le_bytes()); buf.extend_from_slice(&y.to_le_bytes()); } fn push_coord(buf: &mut Vec, coord: [i32; 2], xf: Option) { match xf { Some(xf) => push_coord_xform(buf, coord, xf), None => push_coord_raw(buf, coord), } } fn push_u32(buf: &mut Vec, v: u32) { buf.extend_from_slice(&v.to_le_bytes()); } fn push_rings( buf: &mut Vec, rings: impl IntoIterator>>, xf: Option, ) { for ring in rings { push_u32(buf, ring.0.len() as u32); for c in &ring.0 { push_coord(buf, (*c).into(), xf); } } } fn push_linestring( buf: &mut Vec, line: impl Deref>, xf: Option, ) { buf.push(0x01); push_u32(buf, 2); push_rings(buf, once(line), xf); } fn push_polygon(buf: &mut Vec, poly: &Polygon, xf: Option) { buf.push(0x01); push_u32(buf, 3); push_u32(buf, (poly.interiors().len() + 1) as u32); push_rings(buf, once(poly.exterior()).chain(poly.interiors()), xf); } fn geom32_to_wkb(geom: &Geometry, xf: Option) -> MltResult> { let mut buf = Vec::with_capacity(128); match geom { Geometry::::Point(c) => { buf.push(0x01); push_u32(&mut buf, 1); push_coord(&mut buf, (*c).into(), xf); } Geometry::::LineString(coords) => push_linestring(&mut buf, coords, xf), Geometry::::Polygon(poly) => push_polygon(&mut buf, poly, xf), Geometry::::MultiPoint(coords) => { buf.push(0x01); push_u32(&mut buf, 4); push_u32(&mut buf, coords.0.len() as u32); for c in &coords.0 { buf.push(0x01); push_u32(&mut buf, 1); push_coord(&mut buf, (*c).into(), xf); } } Geometry::::MultiLineString(lines) => { buf.push(0x01); push_u32(&mut buf, 5); push_u32(&mut buf, lines.0.len() as u32); for line in &lines.0 { push_linestring(&mut buf, line, xf); } } Geometry::::MultiPolygon(polygons) => { buf.push(0x01); push_u32(&mut buf, 6); push_u32(&mut buf, polygons.0.len() as u32); for polygon in &polygons.0 { push_polygon(&mut buf, polygon, xf); } } _ => return Err(MltError::NotImplemented("unsupported geometry type")), } Ok(buf) } fn prop_value_to_py(py: Python<'_>, v: PropValueRef<'_>) -> Py { match v { PropValueRef::Bool(b) => b.into_pyobject(py).unwrap().to_owned().into_any().unbind(), PropValueRef::I8(n) => n.into_pyobject(py).unwrap().into_any().unbind(), PropValueRef::U8(n) => n.into_pyobject(py).unwrap().into_any().unbind(), PropValueRef::I32(n) => n.into_pyobject(py).unwrap().into_any().unbind(), PropValueRef::U32(n) => n.into_pyobject(py).unwrap().into_any().unbind(), PropValueRef::I64(n) => n.into_pyobject(py).unwrap().into_any().unbind(), PropValueRef::U64(n) => n.into_pyobject(py).unwrap().into_any().unbind(), PropValueRef::F32(n) => n.into_pyobject(py).unwrap().into_any().unbind(), PropValueRef::F64(n) => n.into_pyobject(py).unwrap().into_any().unbind(), PropValueRef::Str(s) => s.into_pyobject(py).unwrap().into_any().unbind(), } } fn build_features( py: Python<'_>, layer: &ParsedLayer01<'_>, xf: Option, ) -> PyResult>> { let mut features = Vec::new(); let mut feat_iter = layer.iter_features(); while let Some(feat_result) = feat_iter.next() { let feat = feat_result.map_err(mlt_err)?; let geometry_type = GeometryType::try_from(&feat.geometry) .map(|gt| gt.to_string()) .unwrap_or_else(|_| "Unknown".to_string()); let wkb_bytes = geom32_to_wkb(&feat.geometry, xf).map_err(mlt_err)?; let wkb = PyBytes::new(py, &wkb_bytes).unbind(); let prop_dict = PyDict::new(py); for p in feat.iter_properties() { prop_dict.set_item(p.name.to_string(), prop_value_to_py(py, p.value))?; } let feature = MltFeature::new(feat.id, geometry_type, wkb, prop_dict.unbind()); features.push(Py::new(py, feature)?); } Ok(features) } /// Decode an MLT binary blob into a list of `MltLayer` objects. /// /// If `z`, `x`, `y` are provided, tile-local coordinates are transformed /// to EPSG:3857 (Web Mercator) meters. Without them, raw tile coordinates /// are preserved. /// /// `tms`: when True (the default), treat `y` as TMS convention (y=0 at south, /// used by OpenMapTiles / MBTiles). Set to False for XYZ / slippy-map tiles /// (y=0 at north, e.g. OSM raster tiles). #[gen_stub_pyfunction] #[pyfunction] #[pyo3(signature = (data, z=None, x=None, y=None, tms=true))] fn decode_mlt( py: Python<'_>, #[gen_stub(override_type(type_repr = "bytes"))] data: &[u8], z: Option, x: Option, y: Option, tms: bool, ) -> PyResult> { let mut dec = Decoder::default(); let mut result = Vec::new(); for lazy_layer in Parser::default().parse_layers(data).map_err(mlt_err)? { let Layer::Tag01(layer01) = lazy_layer else { return Err(PyValueError::new_err( "unsupported layer tag (expected 0x01)", )); }; let decoded = layer01.decode_all(&mut dec).map_err(mlt_err)?; let xf = match (z, x, y) { (Some(z), Some(x), Some(y)) => { Some(TileTransform::from_zxy(z, x, y, decoded.extent, tms)?) } _ => None, }; result.push(MltLayer { name: decoded.name.to_string(), extent: decoded.extent, features: build_features(py, &decoded, xf)?, }); } Ok(result) } /// Decode an MLT binary blob and return GeoJSON as a string. #[gen_stub_pyfunction] #[pyfunction] fn decode_mlt_to_geojson( #[gen_stub(override_type(type_repr = "bytes"))] data: &[u8], ) -> PyResult { let mut dec = Decoder::default(); let layers = dec .decode_all(Parser::default().parse_layers(data).map_err(mlt_err)?) .map_err(mlt_err)?; let fc = FeatureCollection::from_layers(layers).map_err(mlt_err)?; serde_json::to_string(&fc).map_err(|e| PyValueError::new_err(format!("JSON error: {e}"))) } /// Return a list of layer names without fully decoding. #[gen_stub_pyfunction] #[pyfunction] fn list_layers( #[gen_stub(override_type(type_repr = "bytes"))] data: &[u8], ) -> PyResult> { let layers = Parser::default().parse_layers(data).map_err(mlt_err)?; Ok(layers .iter() .filter_map(|l| l.as_layer01().map(|l| l.name.to_string())) .collect()) } #[pymodule] fn maplibre_tiles(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(decode_mlt, m)?)?; m.add_function(wrap_pyfunction!(decode_mlt_to_geojson, m)?)?; m.add_function(wrap_pyfunction!(list_layers, m)?)?; m.add_class::()?; m.add_class::()?; Ok(()) } define_stub_info_gatherer!(stub_info); #[cfg(test)] mod tests { use std::f64::consts::PI; use std::fs; use mlt_core::{Decoder, GeometryValues}; use super::*; fn geom_to_wkb( geom: &GeometryValues, index: usize, xf: Option, ) -> MltResult> { geom32_to_wkb(&geom.to_geojson(index)?, xf) } #[test] fn tile_transform_rejects_zoom_above_30() { let result = TileTransform::from_zxy(31, 0, 0, 4096, false); assert!(result.is_err(), "z=31 should be rejected"); let result = TileTransform::from_zxy(30, 0, 0, 4096, false); assert!(result.is_ok(), "z=30 should be accepted"); let result = TileTransform::from_zxy(0, 0, 0, 4096, false); assert!(result.is_ok(), "z=0 should be accepted"); } #[test] fn tile_transform_zoom_zero_covers_world() { let xf = TileTransform::from_zxy(0, 0, 0, 4096, false).unwrap(); let circumference = 2.0 * PI * 6_378_137.0; let half = circumference / 2.0; assert!( (xf.x_origin + half).abs() < 1.0, "x_origin at z=0 should be -half_circumference" ); assert!( (xf.y_origin - half).abs() < 1.0, "y_origin at z=0 should be +half_circumference" ); let tile_scale = circumference / 4096.0; assert!( (xf.x_scale - tile_scale).abs() < 1e-6, "x_scale should equal circumference / extent" ); assert!( (xf.y_scale + tile_scale).abs() < 1e-6, "y_scale should be negative (flipped)" ); } #[test] fn tile_transform_apply_maps_origin_and_extent() { let xf = TileTransform::from_zxy(0, 0, 0, 4096, false).unwrap(); let origin = xf.apply([0, 0]); assert!( (origin[0] - xf.x_origin).abs() < 1e-6, "apply([0,0]).x should equal x_origin" ); assert!( (origin[1] - xf.y_origin).abs() < 1e-6, "apply([0,0]).y should equal y_origin" ); let far_corner = xf.apply([4096, 4096]); let circumference = 2.0 * PI * 6_378_137.0; let half = circumference / 2.0; assert!( (far_corner[0] - half).abs() < 1.0, "apply([4096,4096]).x should reach +half" ); assert!( (far_corner[1] + half).abs() < 1.0, "apply([4096,4096]).y should reach -half" ); } #[test] fn tile_transform_tms_vs_xyz() { let xyz = TileTransform::from_zxy(1, 0, 0, 4096, false).unwrap(); let tms = TileTransform::from_zxy(1, 0, 1, 4096, true).unwrap(); assert!( (xyz.x_origin - tms.x_origin).abs() < 1e-6, "same tile via TMS and XYZ should produce same x_origin" ); assert!( (xyz.y_origin - tms.y_origin).abs() < 1e-6, "same tile via TMS and XYZ should produce same y_origin" ); } #[test] fn fixture_parse_and_feature_collection() { let fixture_path = "../../test/synthetic/0x01/point.mlt"; let data = fs::read(fixture_path) .unwrap_or_else(|e| panic!("failed to read fixture {fixture_path}: {e}")); let layers = Parser::default() .parse_layers(&data) .expect("parse_layers should succeed"); let mut dec = Decoder::default(); let decoded = dec.decode_all(layers).expect("decode_all should succeed"); assert!(!decoded.is_empty(), "should parse at least one layer"); let l = decoded[0].as_layer01().expect("first layer should be v0.1"); assert!(!l.name.is_empty(), "layer name should be non-empty"); let fc = FeatureCollection::from_layers(decoded).expect("FeatureCollection should succeed"); assert!( !fc.features.is_empty(), "feature collection should have features" ); } #[test] fn fixture_geom_to_wkb_produces_valid_output() { let fixture_path = "../../test/synthetic/0x01/poly.mlt"; let data = fs::read(fixture_path) .unwrap_or_else(|e| panic!("failed to read fixture {fixture_path}: {e}")); let layers = Parser::default() .parse_layers(&data) .expect("parse_layers should succeed"); let mut dec = Decoder::default(); let decoded = dec.decode_all(layers).expect("decode_all should succeed"); let l = decoded[0].as_layer01().expect("first layer should be v0.1"); let geom = l.geometry_values(); let wkb = geom_to_wkb(geom, 0, None).expect("geom_to_wkb should succeed"); assert!( wkb.len() >= 5, "WKB must be at least 5 bytes (byte order + type)" ); assert_eq!(wkb[0], 0x01, "WKB byte order should be little-endian"); let wkb_type = u32::from_le_bytes([wkb[1], wkb[2], wkb[3], wkb[4]]); assert_eq!( wkb_type, 3, "polygon fixture should produce WKB type 3 (Polygon)" ); } #[test] fn fixture_geom_to_wkb_with_transform() { let fixture_path = "../../test/synthetic/0x01/point.mlt"; let data = fs::read(fixture_path) .unwrap_or_else(|e| panic!("failed to read fixture {fixture_path}: {e}")); let layers = Parser::default() .parse_layers(&data) .expect("parse_layers should succeed"); let mut dec = Decoder::default(); let decoded = dec.decode_all(layers).expect("decode_all should succeed"); let l = decoded[0].as_layer01().expect("first layer should be v0.1"); let geom = l.geometry_values(); let xf = TileTransform::from_zxy(0, 0, 0, l.extent, false).unwrap(); let wkb_raw = geom_to_wkb(geom, 0, None).expect("raw wkb should succeed"); let wkb_xf = geom_to_wkb(geom, 0, Some(xf)).expect("transformed wkb should succeed"); assert_eq!( wkb_raw.len(), wkb_xf.len(), "raw and transformed WKB should have the same length" ); assert_ne!( wkb_raw, wkb_xf, "transformed WKB should differ from raw (unless coordinates are trivially 0)" ); } #[test] fn fixture_line_produces_wkb_linestring() { let fixture_path = "../../test/synthetic/0x01/line.mlt"; let data = fs::read(fixture_path) .unwrap_or_else(|e| panic!("failed to read fixture {fixture_path}: {e}")); let layers = Parser::default() .parse_layers(&data) .expect("parse_layers should succeed"); let mut dec = Decoder::default(); let decoded = dec.decode_all(layers).expect("decode_all should succeed"); let l = decoded[0].as_layer01().expect("first layer should be v0.1"); let geom = l.geometry_values(); let wkb = geom_to_wkb(geom, 0, None).expect("geom_to_wkb should succeed"); assert!(wkb.len() >= 5); let wkb_type = u32::from_le_bytes([wkb[1], wkb[2], wkb[3], wkb[4]]); assert_eq!( wkb_type, 2, "line fixture should produce WKB type 2 (LineString)" ); } } ================================================ FILE: rust/mlt-py/src/tile_transform.rs ================================================ use std::f64::consts::PI; use pyo3::PyErr; use pyo3::exceptions::PyValueError; /// Affine transform from tile-local coords to EPSG:3857 meters. #[derive(Clone, Copy)] pub struct TileTransform { pub x_origin: f64, pub y_origin: f64, pub x_scale: f64, pub y_scale: f64, } impl TileTransform { /// Build a transform from tile z/x/y coordinates. /// /// `tms`: if true, y uses TMS convention (y=0 at south, used by OpenMapTiles /// and MBTiles). If false, y uses XYZ / slippy-map convention (y=0 at north, /// used by OSM tile servers). pub fn from_zxy(z: u32, x: u32, y: u32, extent: u32, tms: bool) -> Result { if z > 30 { return Err(PyValueError::new_err(format!( "zoom level {z} exceeds maximum of 30" ))); } let n = f64::from(1_u32 << z); let circumference = 2.0 * PI * 6_378_137.0; let tile_size = circumference / n; let half = circumference / 2.0; // Convert TMS y to XYZ y if needed (y_xyz = 2^z - 1 - y_tms) let y_xyz = if tms { (1_u32 << z).saturating_sub(1).saturating_sub(y) } else { y }; // In XYZ convention: y=0 is the north edge of the map. // The tile's north (top) edge in EPSG:3857 meters: let x_origin = f64::from(x) * tile_size - half; let y_origin = half - f64::from(y_xyz) * tile_size; let scale = tile_size / f64::from(extent); Ok(TileTransform { x_origin, y_origin, x_scale: scale, y_scale: -scale, // tile pixel-y grows downward, EPSG:3857 y grows upward }) } pub fn apply(self, coord: [i32; 2]) -> [f64; 2] { [ self.x_origin + f64::from(coord[0]) * self.x_scale, self.y_origin + f64::from(coord[1]) * self.y_scale, ] } } ================================================ FILE: rust/mlt-synthetics/Cargo.toml ================================================ [package] name = "mlt-synthetics" version = "0.1.0" authors.workspace = true categories.workspace = true edition.workspace = true homepage.workspace = true keywords.workspace = true license.workspace = true repository.workspace = true rust-version.workspace = true publish = false [dependencies] clap.workspace = true mlt-core = { workspace = true, features = ["__private"] } serde_json.workspace = true thiserror.workspace = true [lints] workspace = true ================================================ FILE: rust/mlt-synthetics/src/layer.rs ================================================ use std::collections::{HashMap, HashSet}; use std::fs::{File, OpenOptions}; use std::io; use std::path::Path; use mlt_core::GeometryValues; use mlt_core::encoder::{ Codecs, ColumnKind, Encoder, EncoderConfig, ExplicitEncoder, IntEncoder, Presence, StagedId, StagedLayer, StagedProperty, StagedSharedDict, StrEncoding, StreamCtx, VertexBufferType, }; use mlt_core::geo_types::{Coord, Geometry}; use mlt_core::wire::{LengthType, OffsetType, StreamType}; use crate::writer::{SynthErr, SynthResult, SynthWriter}; /// Create a layer with all geometry encoders set to `VarInt`. pub fn geo_varint() -> Layer { Layer::new(IntEncoder::varint()) } /// Create a layer with geometry encoders set to `VarInt` and RLE for the meta stream. pub fn geo_varint_with_rle() -> Layer { Layer::new(IntEncoder::varint()).meta(IntEncoder::rle_varint()) } /// Create a layer with all geometry encoders set to `FastPFOR`. pub fn geo_fastpfor() -> Layer { Layer::new(IntEncoder::fastpfor()) } /// Per-property encoding specification. #[derive(Clone)] enum PropConfig { /// Int/Bool/Float: `enc` is used for integer streams; Bool/Float auto-detect from type. Scalar(IntEncoder), /// String FSST encoding. StrFsst { sym_lengths: IntEncoder, dict_lengths: IntEncoder, }, /// String FSST+Dictionary encoding. StrFsstDict { sym_lengths: IntEncoder, dict_lengths: IntEncoder, offsets: IntEncoder, }, /// String Dictionary (plain dict) encoding. StrDict { string_lengths: IntEncoder, offsets: IntEncoder, }, /// Shared dictionary: `StrEncoding` for the corpus, per-suffix `IntEncoder` for offsets. SharedDict { dict_encoding: StrEncoding, item_encs: Vec<(String, IntEncoder)>, }, } impl PropConfig { fn str_encoding(&self) -> StrEncoding { match self { Self::Scalar(_) => StrEncoding::Plain, Self::StrFsst { .. } => StrEncoding::Fsst, Self::StrFsstDict { .. } => StrEncoding::FsstDict, Self::StrDict { .. } => StrEncoding::Dict, Self::SharedDict { dict_encoding, .. } => *dict_encoding, } } /// Resolve the integer encoder for a property stream using wire `StreamType`. fn int_enc_for_stream_ctx(&self, ctx: &StreamCtx<'_>) -> IntEncoder { use LengthType as LT; use OffsetType as OT; use StreamType as ST; match self { Self::Scalar(e) => *e, Self::StrFsst { sym_lengths, dict_lengths, } => match ctx.stream_type { ST::Length(LT::Symbol) => *sym_lengths, _ => *dict_lengths, }, Self::StrFsstDict { sym_lengths, dict_lengths, offsets, } => match ctx.stream_type { ST::Length(LT::Symbol) => *sym_lengths, ST::Offset(OT::String) => *offsets, _ => *dict_lengths, }, Self::StrDict { string_lengths, offsets, } => match ctx.stream_type { ST::Offset(OT::String) => *offsets, _ => *string_lengths, }, Self::SharedDict { item_encs, .. } => { // sub is the item suffix item_encs .iter() .find(|(k, _)| k == ctx.subname) .map(|(_, e)| *e) .or_else(|| item_encs.first().map(|(_, e)| *e)) .unwrap_or_else(IntEncoder::varint) } } } } /// Layer builder for synthetic tile generation. #[derive(Clone)] pub struct Layer { /// Default encoder for all geometry streams. default_geo_enc: IntEncoder, /// Per-stream overrides; key is the stream name (e.g. `"meta"`, `"rings"`). geo_stream_overrides: HashMap<&'static str, IntEncoder>, vertex_buffer_type: VertexBufferType, tessellate: bool, /// Geometry stream names that must be written even when their data is empty. /// See [`ExplicitEncoder::force_stream`] for details. force_empty_streams: HashSet<&'static str>, geometry_items: Vec>, props: Vec<(StagedProperty, PropConfig)>, extent: Option, ids: Option<(StagedId, IntEncoder)>, } impl Layer { fn new(default_enc: IntEncoder) -> Self { Self { default_geo_enc: default_enc, geo_stream_overrides: HashMap::new(), vertex_buffer_type: VertexBufferType::Vec2, tessellate: false, force_empty_streams: HashSet::new(), geometry_items: vec![], props: vec![], extent: None, ids: None, } } #[must_use] pub fn meta(mut self, e: IntEncoder) -> Self { self.geo_stream_overrides.insert("meta", e); self } #[must_use] pub fn rings(mut self, e: IntEncoder) -> Self { self.geo_stream_overrides.insert("rings", e); self } #[must_use] pub fn rings2(mut self, e: IntEncoder) -> Self { self.geo_stream_overrides.insert("rings2", e); self } #[must_use] pub fn no_rings(mut self, e: IntEncoder) -> Self { self.geo_stream_overrides.insert("no_rings", e); self } #[must_use] pub fn parts_ring(mut self, e: IntEncoder) -> Self { self.geo_stream_overrides.insert("parts_ring", e); self } #[must_use] pub fn vertex_offsets(mut self, e: IntEncoder) -> Self { self.geo_stream_overrides.insert("vertex_offsets", e); self } #[must_use] pub fn vertex_buffer_type(mut self, v: VertexBufferType) -> Self { self.vertex_buffer_type = v; self } #[must_use] pub fn tessellate(mut self) -> Self { self.tessellate = true; self } /// Force a geometry stream to be written even when its data is empty. /// /// `name` is the geometry stream name as used internally by the encoder /// (e.g. `"triangles_indexes"`, `"geometries"`, `"rings"`, …). /// /// Useful for producing byte-for-byte output that matches Java's encoder when a /// normally-empty stream must still appear in the wire format. #[must_use] pub fn force_empty_stream(mut self, name: &'static str) -> Self { self.force_empty_streams.insert(name); self } #[must_use] pub fn geo(mut self, geometry: impl Into>) -> Self { self.geometry_items.push(geometry.into()); self } #[must_use] pub fn geos>, I: IntoIterator>( mut self, geometries: I, ) -> Self { for g in geometries { self = self.geo(g.into()); } self } /// Add a bool, integer, or float property. /// /// `enc` is used for integer stream encoding; Bool and Float columns ignore it. #[must_use] pub fn add_prop(mut self, enc: IntEncoder, prop: StagedProperty) -> Self { self.props.push((prop, PropConfig::Scalar(enc))); self } /// Add an FSST-compressed string property. #[must_use] pub fn add_prop_str_fsst( mut self, sym_lengths: IntEncoder, dict_lengths: IntEncoder, prop: StagedProperty, ) -> Self { self.props.push(( prop, PropConfig::StrFsst { sym_lengths, dict_lengths, }, )); self } /// Add a Dictionary (plain dict) string property. #[must_use] pub fn add_prop_str_dict( mut self, string_lengths: IntEncoder, offsets: IntEncoder, prop: StagedProperty, ) -> Self { self.props.push(( prop, PropConfig::StrDict { string_lengths, offsets, }, )); self } /// Add an FSST+Dictionary string property. #[must_use] pub fn add_prop_str_fsst_dict( mut self, sym_lengths: IntEncoder, dict_lengths: IntEncoder, offsets: IntEncoder, prop: StagedProperty, ) -> Self { self.props.push(( prop, PropConfig::StrFsstDict { sym_lengths, dict_lengths, offsets, }, )); self } /// Add a shared dictionary column. #[must_use] pub fn add_shared_dict(mut self, shared_dict: SharedDict) -> Self { let dict_encoding = shared_dict.dict_encoding; let item_encs: Vec<(String, IntEncoder)> = shared_dict .items .iter() .map(|(suffix, enc, _, _)| (suffix.clone(), *enc)) .collect(); let dict = StagedSharedDict::new( shared_dict.name, shared_dict .items .into_iter() .map(|(suffix, _, vals, is_optional)| { let presence = if is_optional { Presence::Mixed } else { Presence::AllPresent }; (suffix, vals, presence) }), ) .expect("shared dict builder should be valid"); self.props.push(( StagedProperty::SharedDict(dict), PropConfig::SharedDict { dict_encoding, item_encs, }, )); self } /// Encode and then either verify against the reference dir (non-rust files) or write to the /// output dir (`-rust`-suffixed files). Delegates to [`SynthWriter::write`]. /// /// When `force_empty_streams` is non-empty, also emits a `_ns` ("no forced stream") /// sibling — but only when removing the forced-empty-stream flag **actually changes the /// encoded output**. For some geometry configurations (e.g. Multi* types where the /// GEOMETRIES stream is already non-empty) the flag is a no-op; emitting the sibling in /// those cases would produce duplicate MLT files and fail the uniqueness check. pub fn write(self, w: &mut SynthWriter, name: impl AsRef) { if !self.force_empty_streams.is_empty() { let forced_bytes = self.clone().encode_to_bytes().ok(); let mut ns_layer = self.clone(); ns_layer.force_empty_streams.clear(); let ns_bytes = ns_layer.clone().encode_to_bytes().ok(); if forced_bytes != ns_bytes { let name = if let Some(prefix) = name.as_ref().strip_suffix("-rust") { format!("{prefix}_ns-rust") } else { format!("{}_ns", name.as_ref()) }; w.write(ns_layer, name); } } w.write(self, name); } #[must_use] pub fn extent(mut self, extent: u32) -> Self { self.extent = Some(extent); self } /// Set feature IDs with explicit encoding. #[must_use] pub fn ids(mut self, ids: StagedId, int_enc: IntEncoder) -> Self { self.ids = Some((ids, int_enc)); self } pub fn open_new(path: &Path) -> io::Result { OpenOptions::new().write(true).create_new(true).open(path) } pub fn encode_to_bytes(self) -> SynthResult> { let Self { default_geo_enc, geo_stream_overrides, vertex_buffer_type, tessellate, force_empty_streams, geometry_items, props, extent, ids, } = self; let enc_cfg = EncoderConfig { tessellate, ..EncoderConfig::default() }; let mut geometry = if enc_cfg.tessellate { GeometryValues::new_tessellated() } else { GeometryValues::default() }; for geom in &geometry_items { geometry.push_geom(geom); } let (id, id_int_enc) = match ids { Some((ids, int_enc)) => (ids, Some(int_enc)), None => (StagedId::None, None), }; // Build name→PropConfig map for the ExplicitEncoder callbacks. let prop_map: HashMap = props .iter() .map(|(p, c)| (p.name().to_string(), c.clone())) .collect(); let cfg = ExplicitEncoder { vertex_buffer_type, force_stream: Box::new(move |ctx: &StreamCtx<'_>| { ctx.kind == ColumnKind::Geometry && force_empty_streams.contains(ctx.name) }), get_int_encoder: { let prop_map = prop_map.clone(); Box::new(move |ctx: &StreamCtx<'_>| match ctx.kind { ColumnKind::Id => id_int_enc.unwrap_or_else(IntEncoder::varint), ColumnKind::Geometry => geo_stream_overrides .get(ctx.name) .copied() .unwrap_or(default_geo_enc), ColumnKind::Property => prop_map .get(ctx.name) .map_or_else(IntEncoder::varint, |c| c.int_enc_for_stream_ctx(ctx)), }) }, get_str_encoding: { Box::new(move |name: &str| { prop_map .get(name) .map_or(StrEncoding::Plain, PropConfig::str_encoding) }) }, }; let mut codecs = Codecs::default(); StagedLayer { name: "layer1".to_string(), extent: extent.unwrap_or(80), id, geometry, properties: props.into_iter().map(|(p, _)| p).collect(), } .encode_into(Encoder::with_explicit(enc_cfg, cfg), &mut codecs)? .into_layer_bytes() .map_err(SynthErr::Mlt) } } /// Builder for a shared dictionary struct column with multiple string sub-properties. pub struct SharedDict { name: String, dict_encoding: StrEncoding, /// `(suffix, encoder, values, is_optional)` items: Vec<(String, IntEncoder, Vec>, bool)>, } impl SharedDict { /// Create a new shared dictionary builder. /// /// # Arguments /// * `name` - The name for the property (e.g., `"name:"` for `"name:de"`, `"name:en"`). /// * `dict_encoding` - The string encoding for the shared dictionary corpus (plain or FSST). #[must_use] pub fn new(name: impl Into, dict_encoding: StrEncoding) -> Self { Self { name: name.into(), dict_encoding, items: vec![], } } /// Add a non-optional child column (no presence stream will be written). #[must_use] pub fn col>( mut self, suffix: impl Into, offsets: IntEncoder, values: impl IntoIterator, ) -> Self { self.items.push(( suffix.into(), offsets, values.into_iter().map(|v| Some(v.into())).collect(), false, )); self } /// Add an optional child column (a presence stream is always written). #[must_use] pub fn opt( mut self, suffix: impl Into, offsets: IntEncoder, values: impl IntoIterator>, ) -> Self { self.items .push((suffix.into(), offsets, values.into_iter().collect(), true)); self } } /// Morton (Z-order) curve: de-interleave index bits into x/y (even/odd bits). /// Produces a 4×4 complete Morton block (16 points, scale 8). pub fn morton_curve() -> Vec> { let num_points = 16usize; let scale = 8_i32; let morton_bits = 4u32; let mut curve = Vec::with_capacity(num_points); for i in 0..num_points { let i = i32::try_from(i).unwrap(); let mut x = 0_i32; let mut y = 0_i32; for b in 0..morton_bits { x |= ((i >> (2 * b)) & 1) << b; y |= ((i >> (2 * b + 1)) & 1) << b; } curve.push(crate::c(x * scale, y * scale)); } curve } ================================================ FILE: rust/mlt-synthetics/src/main.rs ================================================ //! Rust synthetic MLT file generator. //! //! Verifies non-rust synthetics in-memory against the reference `0x01/` dir. //! //! * If the test exists in `0x01/` dir, validates that Rust-generated one is identical //! * If the test does not match the one in `0x01/` dir, it gets written to `0x01-rust/` dir. //! * Tests with `_fsst` in their name are expected to produce different-but-compatible output, //! and their output is placed into `0x01-rust/` dir. //! * Tests with the `-rust` suffix are also written to `0x01-rust/` dir, except without the suffix. //! * All tests written to `0x01-rust/` are also validated against the .json file with the same name in `0x01/` //! //! ### Common filename abbreviations //! * `np` - no presence stream, i.e. values exist for each feature in a column //! * `fpf` - uses `FastPFor` compression //! * `tes` - includes tessellation triangles stream //! * `ns` - unlike Java encoder, empty streams are not forced to be created mod layer; mod writer; use std::fmt::Write as _; use std::path::PathBuf; use std::sync::LazyLock; use clap::Parser; use mlt_core::encoder::{ IntEncoder as E, LogicalEncoder as L, StagedId as Id, StagedProperty as P, StrEncoding, VertexBufferType, }; use mlt_core::geo_types::{ Coord, Geometry, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, coord, line_string as line, wkt, }; use crate::layer::{ Layer, SharedDict, geo_fastpfor, geo_varint, geo_varint_with_rle, morton_curve, }; use crate::writer::SynthWriter; #[derive(Parser)] #[command(about = "Verify Rust-generated synthetic MLTs against the Java reference")] struct Args { /// Print each verified or written file #[arg(long)] verbose: bool, /// Directory with the reference synthetic MLT files to verify against (must exist) #[arg(long, default_value = "../test/synthetic/0x01/")] synthetics: PathBuf, /// Directory to use for Rust-specific synthetic MLT files (will be created if it doesn't exist) #[arg(long, default_value = "../test/synthetic/0x01-rust/")] synthetics_rust: PathBuf, } const C0: Coord = coord! { x: 13, y: 42 }; // triangle 1, clockwise winding, X ends in 1, Y ends in 2 const C1: Coord = coord! { x: 11, y: 52 }; const C2: Coord = coord! { x: 71, y: 72 }; const C3: Coord = coord! { x: 61, y: 22 }; // hole in triangle 1 with counter-clockwise winding const H1: Coord = coord! { x: 65, y: 66 }; const H2: Coord = coord! { x: 35, y: 56 }; const H3: Coord = coord! { x: 55, y: 36 }; const P0: Point = Point(C0); const P1: Point = Point(C1); const P2: Point = Point(C2); const P3: Point = Point(C3); // holes as points with same coordinates as the hole vertices const PH1: Point = Point(H1); const PH2: Point = Point(H2); const PH3: Point = Point(H3); const fn c(x: i32, y: i32) -> Coord { coord! { x: x, y: y } } fn p0() -> Layer { geo_varint().geo(P0) } static MIX_TYPES: LazyLock<[(&'static str, Geometry); 7]> = LazyLock::new(|| { [ ("pt", wkt!(POINT(38 29)).into()), ("line", wkt!(LINESTRING(5 38, 12 45, 9 70)).into()), ("poly", wkt!(POLYGON((55 5, 58 28, 75 22, 55 5))).into()), ( "polyh", wkt!(POLYGON((52 35, 14 55, 60 72, 52 35),(32 50, 36 60, 24 54, 32 50))).into(), ), ("mpt", wkt!(MULTIPOINT(6 25, 21 41, 23 69)).into()), ( "mline", wkt!(MULTILINESTRING((24 10, 42 18),(30 36, 48 52, 35 62))).into(), ), ( "mpoly", wkt!(MULTIPOLYGON(((7 20, 21 31, 26 9, 7 20),(15 20, 20 15, 18 25, 15 20)),((69 57, 71 66, 73 64, 69 57)))).into(), ), ] }); fn main() { let mut writer = SynthWriter::new(Args::parse()); generate_geometry(&mut writer); generate_mixed(&mut writer); generate_extent(&mut writer); generate_ids(&mut writer); generate_properties(&mut writer); writer.report_ungenerated(); if writer.failures > 0 { eprintln!("{} synthetics failed", writer.failures); std::process::exit(1); } } // Geometry builder functions matching Java definitions fn line1() -> LineString { wkt!(LINESTRING(11 52, 71 72, 61 22)) } fn line2() -> LineString { wkt!(LINESTRING(23 34, 73 4, 13 24)) } fn poly1() -> Polygon { wkt!(POLYGON((11 52, 71 72, 61 22, 11 52))) } fn poly2() -> Polygon { wkt!(POLYGON((23 34, 73 4, 13 24, 23 34))) } fn poly1h() -> Polygon { wkt!(POLYGON((11 52, 71 72, 61 22, 11 52),(65 66, 35 56, 55 36, 65 66))) } fn poly_collinear() -> Polygon { wkt!(POLYGON((0 0, 10 0, 20 0, 0 0))) } fn poly_self_intersect() -> Polygon { wkt!(POLYGON((0 0, 10 10, 0 10, 10 0, 0 0))) } fn poly_hole_touching() -> Polygon { wkt!(POLYGON((0 0, 10 0, 10 10, 0 10, 0 0),(0 0, 2 2, 5 2, 0 0))) } fn generate_geometry(w: &mut SynthWriter) { p0().write(w, "point"); geo_varint().geo(line1()).write(w, "line"); geo_varint() .geo(LineString::new(morton_curve())) .vertex_buffer_type(VertexBufferType::Morton) .vertex_offsets(E::delta_rle_varint()) .write(w, "line_morton_curve_morton"); geo_varint() .geo(LineString::new(morton_curve())) .vertex_buffer_type(VertexBufferType::Vec2) .vertex_offsets(E::delta_rle_varint()) .write(w, "line_morton_curve_no_morton"); geo_varint() .geo(LineString::new(vec![c(6_i32, 6), c(6, 6)])) .write(w, "line_zero_length"); geo_varint().geo(poly1()).write(w, "poly"); geo_fastpfor().geo(poly1()).write(w, "poly_fpf"); geo_varint() .tessellate() .force_empty_stream("geometries") .geo(poly1()) .write(w, "poly_tes"); geo_fastpfor() .tessellate() .force_empty_stream("geometries") .geo(poly1()) .write(w, "poly_fpf_tes"); geo_varint() .geo(poly_collinear()) .write(w, "poly_collinear"); geo_fastpfor() .geo(poly_collinear()) .write(w, "poly_collinear_fpf"); geo_varint() .tessellate() .force_empty_stream("geometries") .force_empty_stream("triangles_indexes") .geo(poly_collinear()) .write(w, "poly_collinear_tes"); geo_fastpfor() .tessellate() .force_empty_stream("geometries") .force_empty_stream("triangles_indexes") .geo(poly_collinear()) .write(w, "poly_collinear_fpf_tes"); geo_varint() .geo(poly_self_intersect()) .write(w, "poly_self_intersect"); geo_fastpfor() .geo(poly_self_intersect()) .write(w, "poly_self_intersect_fpf"); geo_varint() .tessellate() .force_empty_stream("geometries") .geo(poly_self_intersect()) .write(w, "poly_self_intersect_tes"); geo_fastpfor() .tessellate() .force_empty_stream("geometries") .geo(poly_self_intersect()) .write(w, "poly_self_intersect_fpf_tes"); geo_varint() .parts_ring(E::rle_varint()) .geo(poly1h()) .write(w, "poly_hole"); geo_fastpfor() .parts_ring(E::rle_fastpfor()) .geo(poly1h()) .write(w, "poly_hole_fpf"); geo_varint() .parts_ring(E::rle_varint()) .tessellate() .force_empty_stream("geometries") .geo(poly1h()) .write(w, "poly_hole_tes"); geo_fastpfor() .parts_ring(E::rle_fastpfor()) .tessellate() .force_empty_stream("geometries") .geo(poly1h()) .write(w, "poly_hole_fpf_tes"); geo_varint() .parts_ring(E::varint()) .geo(poly_hole_touching()) .write(w, "poly_hole_touching"); geo_fastpfor() .parts_ring(E::fastpfor()) .geo(poly_hole_touching()) .write(w, "poly_hole_touching_fpf"); geo_varint() .parts_ring(E::varint()) .tessellate() .force_empty_stream("geometries") .geo(poly_hole_touching()) .write(w, "poly_hole_touching_tes"); geo_fastpfor() .parts_ring(E::fastpfor()) .tessellate() .force_empty_stream("geometries") .geo(poly_hole_touching()) .write(w, "poly_hole_touching_fpf_tes"); geo_varint() .rings(E::rle_varint()) .rings2(E::rle_varint()) .geo(MultiPolygon(vec![poly1(), poly2()])) .write(w, "poly_multi"); geo_fastpfor() .rings(E::rle_fastpfor()) .rings2(E::rle_fastpfor()) .geo(MultiPolygon(vec![poly1(), poly2()])) .write(w, "poly_multi_fpf"); geo_varint() .rings(E::rle_varint()) .rings2(E::rle_varint()) .tessellate() .geo(MultiPolygon(vec![poly1(), poly2()])) .write(w, "poly_multi_tes"); geo_fastpfor() .rings(E::rle_fastpfor()) .rings2(E::rle_fastpfor()) .tessellate() .geo(MultiPolygon(vec![poly1(), poly2()])) .write(w, "poly_multi_fpf_tes"); // Close the shared Morton curve into a ring to test Morton encoding for polygons. let mut morton_ring = morton_curve(); morton_ring.push(morton_ring[0]); let morton_poly = Polygon::new(LineString::new(morton_ring), vec![]); geo_varint() .geo(morton_poly.clone()) .write(w, "poly_morton_ring_no_morton"); geo_varint() .vertex_buffer_type(VertexBufferType::Morton) .vertex_offsets(E::delta_rle_varint()) .geo(morton_poly) .write(w, "poly_morton_ring_morton"); // Split the Morton curve into two halves and close each into a ring to form a MultiPolygon. let mc = morton_curve(); let half = mc.len() / 2; let mut mr1 = mc[..half].to_vec(); mr1.push(mr1[0]); let mut mr2 = mc[half..].to_vec(); mr2.push(mr2[0]); let mp_morton = MultiPolygon(vec![ Polygon::new(LineString::new(mr1), vec![]), Polygon::new(LineString::new(mr2), vec![]), ]); geo_varint() .rings(E::rle_varint()) .rings2(E::rle_varint()) .geo(mp_morton.clone()) .write(w, "poly_multi_morton_ring_no_morton"); geo_varint() .rings(E::rle_varint()) .rings2(E::rle_varint()) .vertex_buffer_type(VertexBufferType::Morton) .vertex_offsets(E::delta_rle_varint()) .geo(mp_morton) .write(w, "poly_multi_morton_ring_morton"); geo_varint() .geo(MultiPoint(vec![P1, P2, P3])) .write(w, "multipoint"); // Split the Morton curve at a different place so that the rings are different lengths, // use one as the shell and one as the hole of a single and multi-polygon. let quarter = mc.len() / 4; let mut mr_shell = mc[..quarter].to_vec(); mr_shell.push(mr_shell[0]); let mut mr_hole = mc[quarter..].to_vec(); mr_hole.push(mr_hole[0]); let poly_with_hole = Polygon::new(LineString::new(mr_shell), vec![LineString::new(mr_hole)]); geo_varint() .vertex_buffer_type(VertexBufferType::Morton) .vertex_offsets(E::delta_rle_varint()) .geo(poly_with_hole.clone()) .write(w, "poly_morton_hole_morton"); geo_varint() .vertex_buffer_type(VertexBufferType::Morton) .vertex_offsets(E::delta_rle_varint()) .geo(MultiPolygon(vec![poly_with_hole])) .write(w, "poly_multi_morton_hole_morton"); geo_varint() .no_rings(E::rle_varint()) .geo(MultiLineString(vec![line1(), line2()])) .write(w, "multiline"); // Split the Morton curve into two halves to form a MultiLineString with Morton encoding. let mline1 = LineString::new(mc[..half].to_vec()); let mline2 = LineString::new(mc[half..].to_vec()); geo_varint() .no_rings(E::rle_varint()) .vertex_buffer_type(VertexBufferType::Morton) .vertex_offsets(E::delta_rle_varint()) .geo(MultiLineString(vec![mline1, mline2])) .write(w, "multiline_morton"); } fn write_mix(w: &mut SynthWriter, current: &[usize]) { let mut builder = geo_varint(); let mut builder_t = Some(geo_varint().tessellate()); let mut name = format!("mix_{}", current.len()); for idx in current { let mix_type = &MIX_TYPES[*idx]; builder = builder.geo(mix_type.1.clone()); write!(&mut name, "_{}", mix_type.0).unwrap(); if let Some(bldr) = builder_t { if matches!( mix_type.1, Geometry::::Polygon(_) | Geometry::::MultiPolygon(_) ) { builder_t = Some(bldr.geo(mix_type.1.clone())); } else { builder_t = None; } } } if let Some(bldr) = builder_t { // let suffix = if ["..."].contains(name) { "" } else { "-rust" }; let suffix = ""; bldr.force_empty_stream("geometries") .write(w, format!("{name}_tes{suffix}")); } builder.write(w, &name); } fn generate_combinations(w: &mut SynthWriter, k: usize, start: usize, current: &mut Vec) { if current.len() == k { write_mix(w, current); } else { for i in start..MIX_TYPES.len() { current.push(i); generate_combinations(w, k, i + 1, current); current.pop(); } } } fn generate_mixed(w: &mut SynthWriter) { // Generate all combinations of MIX_TYPES with length 2 or more for k in 2..=MIX_TYPES.len() { generate_combinations(w, k, 0, &mut Vec::new()); } // Generate A-A (duplicate) and A-B-A patterns for idx in 0..MIX_TYPES.len() { write_mix(w, &[idx, idx]); // A-A variant for idx2 in 0..MIX_TYPES.len() { if idx != idx2 { write_mix(w, &[idx, idx2, idx]); // A-B-A variant } } } } fn generate_extent(w: &mut SynthWriter) { for e in [512_i32, 4096, 131_072, 1_073_741_824] { geo_varint() .extent(e.cast_unsigned()) .geo(line![c(0_i32, 0), c(e - 1, e - 1)]) .write(w, format!("extent_{e}")); geo_varint() .extent(e.cast_unsigned()) .geo(line![c(-42_i32, -42), c(e + 42, e + 42)]) .write(w, format!("extent_buf_{e}")); } } fn generate_ids(w: &mut SynthWriter) { p0().ids(Id::u32(vec![100]), E::varint_with(L::None)) .write(w, "id"); p0().ids(Id::u32(vec![u32::MIN]), E::varint_with(L::None)) .write(w, "id_min"); p0().ids(Id::u32(vec![u32::MAX]), E::varint_with(L::None)) .write(w, "id_max"); p0().ids(Id::u64(vec![9_234_567_890]), E::varint_with(L::None)) .write(w, "id64"); p0().ids(Id::u64(vec![u64::MAX]), E::varint_with(L::None)) .write(w, "id64_max"); let four_p0 = || geo_varint_with_rle().geos([P0, P0, P0, P0]); let dup_id = || Id::u32(vec![103; 4]); let dup_u64_id = || Id::u64(vec![9_234_567_890; 4]); four_p0() .ids(dup_id(), E::varint_with(L::None)) .write(w, "ids"); four_p0() .ids(dup_id(), E::varint_with(L::Delta)) .write(w, "ids_delta"); four_p0() .ids(dup_id(), E::varint_with(L::Rle)) .write(w, "ids_rle"); four_p0() .ids(dup_id(), E::varint_with(L::DeltaRle)) .write(w, "ids_delta_rle"); four_p0() .ids(dup_u64_id(), E::varint_with(L::None)) .write(w, "ids64"); four_p0() .ids(dup_u64_id(), E::varint_with(L::Delta)) .write(w, "ids64_delta"); four_p0() .ids(dup_u64_id(), E::varint_with(L::Rle)) .write(w, "ids64_rle"); four_p0() .ids(dup_u64_id(), E::varint_with(L::DeltaRle)) .write(w, "ids64_delta_rle"); let five_p0 = || geo_varint_with_rle().geos([P0, P0, P0, P0, P0]); five_p0() .ids( Id::opt_u32(vec![Some(100), Some(101), None, Some(105), Some(106)]), E::varint_with(L::None), ) .write(w, "ids_opt"); five_p0() .ids( Id::opt_u32(vec![Some(100), Some(101), None, Some(105), Some(106)]), E::varint_with(L::Delta), ) .write(w, "ids_opt_delta"); five_p0() .ids( Id::opt_u64(vec![ None, Some(9_234_567_890), Some(101), Some(105), Some(106), ]), E::varint_with(L::None), ) .write(w, "ids64_opt"); five_p0() .ids( Id::opt_u64(vec![ None, Some(9_234_567_890), Some(101), Some(105), Some(106), ]), E::varint_with(L::Delta), ) .write(w, "ids64_opt_delta"); let min_max = || Id::u64(vec![u64::MIN, u64::MAX, u64::MIN, u64::MAX]); four_p0() .ids(min_max(), E::varint_with(L::None)) .write(w, "ids64_minmax"); four_p0() .ids(min_max(), E::varint_with(L::Delta)) .write(w, "ids64_minmax_delta"); // FastPFOR physical encoding for u32 IDs (Rust-only: Java encoder does not support this) four_p0().ids(dup_id(), E::fastpfor()).write(w, "ids_fpf"); four_p0() .ids(dup_id(), E::delta_fastpfor()) .write(w, "ids_delta_fpf"); } fn generate_properties(w: &mut SynthWriter) { // Properties with special names let e_any = E::varint(); p0().add_prop(e_any, P::bool("", vec![true])) .write(w, "prop_empty_name_np"); p0().add_prop(e_any, P::opt_bool("", vec![Some(true)])) .write(w, "prop_empty_name"); p0().add_prop(e_any, P::bool("hello\u{0000} world\n", vec![true])) .write(w, "prop_special_name_np"); p0().add_prop( e_any, P::opt_bool("hello\u{0000} world\n", vec![Some(true)]), ) .write(w, "prop_special_name"); p0().add_prop(e_any, P::bool("val", vec![true])) .write(w, "prop_bool_np"); p0().add_prop(e_any, P::opt_bool("val", vec![Some(true)])) .write(w, "prop_bool"); p0().add_prop(e_any, P::bool("val", vec![false])) .write(w, "prop_bool_false_np"); p0().add_prop(e_any, P::opt_bool("val", vec![Some(false)])) .write(w, "prop_bool_false"); // Two-feature optional bool variants geo_varint_with_rle() .geos([P0, P0]) .add_prop(e_any, P::opt_bool("val", vec![Some(true), None])) .write(w, "prop_bool_true_null"); geo_varint_with_rle() .geos([P0, P0]) .add_prop(e_any, P::opt_bool("val", vec![None, Some(true)])) .write(w, "prop_bool_null_true"); geo_varint_with_rle() .geos([P0, P0]) .add_prop(e_any, P::opt_bool("val", vec![Some(false), None])) .write(w, "prop_bool_false_null"); geo_varint_with_rle() .geos([P0, P0]) .add_prop(e_any, P::opt_bool("val", vec![None, Some(false)])) .write(w, "prop_bool_null_false"); let e_int = E::varint(); p0().add_prop(e_int, P::i32("val", vec![42])) .write(w, "prop_i32_np"); p0().add_prop(e_int, P::opt_i32("val", vec![Some(42)])) .write(w, "prop_i32"); p0().add_prop(e_int, P::i32("val", vec![-42])) .write(w, "prop_i32_neg_np"); p0().add_prop(e_int, P::opt_i32("val", vec![Some(-42)])) .write(w, "prop_i32_neg"); p0().add_prop(e_int, P::i32("val", vec![i32::MIN])) .write(w, "prop_i32_min_np"); p0().add_prop(e_int, P::opt_i32("val", vec![Some(i32::MIN)])) .write(w, "prop_i32_min"); p0().add_prop(e_int, P::i32("val", vec![i32::MAX])) .write(w, "prop_i32_max_np"); p0().add_prop(e_int, P::opt_i32("val", vec![Some(i32::MAX)])) .write(w, "prop_i32_max"); // Two-feature optional i32 variants geo_varint_with_rle() .geos([P0, P0]) .add_prop(e_int, P::opt_i32("val", vec![Some(42), None])) .write(w, "prop_i32_val_null"); geo_varint_with_rle() .geos([P0, P0]) .add_prop(e_int, P::opt_i32("val", vec![None, Some(42)])) .write(w, "prop_i32_null_val"); p0().add_prop(e_int, P::u32("val", vec![42])) .write(w, "prop_u32_np"); p0().add_prop(e_int, P::opt_u32("val", vec![Some(42)])) .write(w, "prop_u32"); p0().add_prop(e_int, P::u32("val", vec![0])) .write(w, "prop_u32_min_np"); p0().add_prop(e_int, P::opt_u32("val", vec![Some(0)])) .write(w, "prop_u32_min"); p0().add_prop(e_int, P::u32("val", vec![u32::MAX])) .write(w, "prop_u32_max_np"); p0().add_prop(e_int, P::opt_u32("val", vec![Some(u32::MAX)])) .write(w, "prop_u32_max"); // Two-feature optional u32 variants geo_varint_with_rle() .geos([P0, P0]) .add_prop(e_int, P::opt_u32("val", vec![Some(42), None])) .write(w, "prop_u32_val_null"); geo_varint_with_rle() .geos([P0, P0]) .add_prop(e_int, P::opt_u32("val", vec![None, Some(42)])) .write(w, "prop_u32_null_val"); p0().add_prop(e_int, P::i64("val", vec![9_876_543_210])) .write(w, "prop_i64_np"); p0().add_prop(e_int, P::opt_i64("val", vec![Some(9_876_543_210)])) .write(w, "prop_i64"); p0().add_prop(e_int, P::i64("val", vec![-9_876_543_210])) .write(w, "prop_i64_neg_np"); p0().add_prop(e_int, P::opt_i64("val", vec![Some(-9_876_543_210)])) .write(w, "prop_i64_neg"); p0().add_prop(e_int, P::i64("val", vec![i64::MIN])) .write(w, "prop_i64_min_np"); p0().add_prop(e_int, P::opt_i64("val", vec![Some(i64::MIN)])) .write(w, "prop_i64_min"); p0().add_prop(e_int, P::i64("val", vec![i64::MAX])) .write(w, "prop_i64_max_np"); p0().add_prop(e_int, P::opt_i64("val", vec![Some(i64::MAX)])) .write(w, "prop_i64_max"); // Two-feature optional i64 variants geo_varint_with_rle() .geos([P0, P0]) .add_prop(e_int, P::opt_i64("val", vec![Some(9_876_543_210), None])) .write(w, "prop_i64_val_null"); geo_varint_with_rle() .geos([P0, P0]) .add_prop(e_int, P::opt_i64("val", vec![None, Some(9_876_543_210)])) .write(w, "prop_i64_null_val"); p0().add_prop(e_int, P::u64("bignum", vec![1_234_567_890_123_456_789])) .write(w, "prop_u64_np"); p0().add_prop( e_int, P::opt_u64("bignum", vec![Some(1_234_567_890_123_456_789)]), ) .write(w, "prop_u64"); p0().add_prop(e_int, P::u64("bignum", vec![0])) .write(w, "prop_u64_min_np"); p0().add_prop(e_int, P::opt_u64("bignum", vec![Some(0)])) .write(w, "prop_u64_min"); p0().add_prop(e_int, P::u64("bignum", vec![u64::MAX])) .write(w, "prop_u64_max_np"); p0().add_prop(e_int, P::opt_u64("bignum", vec![Some(u64::MAX)])) .write(w, "prop_u64_max"); // Two-feature optional u64 variants (key is "val" to match Java) geo_varint_with_rle() .geos([P0, P0]) .add_prop( e_int, P::opt_u64("val", vec![Some(1_234_567_890_123_456_789), None]), ) .write(w, "prop_u64_val_null"); geo_varint_with_rle() .geos([P0, P0]) .add_prop( e_int, P::opt_u64("val", vec![None, Some(1_234_567_890_123_456_789)]), ) .write(w, "prop_u64_null_val"); let e_fl = E::varint(); #[expect(clippy::approx_constant)] p0().add_prop(e_fl, P::f32("val", vec![3.14])) .write(w, "prop_f32_np"); #[expect(clippy::approx_constant)] p0().add_prop(e_fl, P::opt_f32("val", vec![Some(3.14)])) .write(w, "prop_f32"); p0().add_prop(e_fl, P::f32("val", vec![f32::NEG_INFINITY])) .write(w, "prop_f32_neg_inf_np"); p0().add_prop(e_fl, P::opt_f32("val", vec![Some(f32::NEG_INFINITY)])) .write(w, "prop_f32_neg_inf"); p0().add_prop(e_fl, P::f32("val", vec![f32::from_bits(1)])) .write(w, "prop_f32_min_val_np"); p0().add_prop(e_fl, P::opt_f32("val", vec![Some(f32::from_bits(1))])) .write(w, "prop_f32_min_val"); p0().add_prop(e_fl, P::f32("val", vec![f32::MIN_POSITIVE])) .write(w, "prop_f32_min_norm_np"); p0().add_prop(e_fl, P::opt_f32("val", vec![Some(f32::MIN_POSITIVE)])) .write(w, "prop_f32_min_norm"); p0().add_prop(e_fl, P::f32("val", vec![0.0])) .write(w, "prop_f32_zero_np"); p0().add_prop(e_fl, P::opt_f32("val", vec![Some(0.0)])) .write(w, "prop_f32_zero"); p0().add_prop(e_fl, P::f32("val", vec![-0.0])) .write(w, "prop_f32_neg_zero_np"); p0().add_prop(e_fl, P::opt_f32("val", vec![Some(-0.0)])) .write(w, "prop_f32_neg_zero"); p0().add_prop(e_fl, P::f32("val", vec![f32::MAX])) .write(w, "prop_f32_max_np"); p0().add_prop(e_fl, P::opt_f32("val", vec![Some(f32::MAX)])) .write(w, "prop_f32_max"); p0().add_prop(e_fl, P::f32("val", vec![f32::INFINITY])) .write(w, "prop_f32_pos_inf_np"); p0().add_prop(e_fl, P::opt_f32("val", vec![Some(f32::INFINITY)])) .write(w, "prop_f32_pos_inf"); p0().add_prop(e_fl, P::f32("val", vec![f32::NAN])) .write(w, "prop_f32_nan_np"); p0().add_prop(e_fl, P::opt_f32("val", vec![Some(f32::NAN)])) .write(w, "prop_f32_nan"); // Two-feature optional f32 variants #[expect(clippy::approx_constant)] geo_varint_with_rle() .geos([P0, P0]) .add_prop(e_fl, P::opt_f32("val", vec![Some(3.14), None])) .write(w, "prop_f32_val_null"); #[expect(clippy::approx_constant)] geo_varint_with_rle() .geos([P0, P0]) .add_prop(e_fl, P::opt_f32("val", vec![None, Some(3.14)])) .write(w, "prop_f32_null_val"); p0().add_prop(e_fl, P::f64("val", vec![std::f64::consts::PI])) .write(w, "prop_f64_np"); p0().add_prop(e_fl, P::opt_f64("val", vec![Some(std::f64::consts::PI)])) .write(w, "prop_f64"); p0().add_prop(e_fl, P::f64("val", vec![f64::NAN])) .write(w, "prop_f64_nan_np"); p0().add_prop(e_fl, P::opt_f64("val", vec![Some(f64::NAN)])) .write(w, "prop_f64_nan"); p0().add_prop(e_fl, P::f64("val", vec![f64::NEG_INFINITY])) .write(w, "prop_f64_neg_inf_np"); p0().add_prop(e_fl, P::opt_f64("val", vec![Some(f64::NEG_INFINITY)])) .write(w, "prop_f64_neg_inf"); p0().add_prop(e_fl, P::f64("val", vec![f64::from_bits(1)])) .write(w, "prop_f64_min_val_np"); p0().add_prop(e_fl, P::opt_f64("val", vec![Some(f64::from_bits(1))])) .write(w, "prop_f64_min_val"); p0().add_prop(e_fl, P::f64("val", vec![f64::MIN_POSITIVE])) .write(w, "prop_f64_min_norm_np"); p0().add_prop(e_fl, P::opt_f64("val", vec![Some(f64::MIN_POSITIVE)])) .write(w, "prop_f64_min_norm"); p0().add_prop(e_fl, P::f64("val", vec![-0.0_f64])) .write(w, "prop_f64_neg_zero_np"); p0().add_prop(e_fl, P::opt_f64("val", vec![Some(-0.0_f64)])) .write(w, "prop_f64_neg_zero"); p0().add_prop(e_fl, P::f64("val", vec![0.0_f64])) .write(w, "prop_f64_zero_np"); p0().add_prop(e_fl, P::opt_f64("val", vec![Some(0.0_f64)])) .write(w, "prop_f64_zero"); p0().add_prop(e_fl, P::f64("val", vec![f64::MAX])) .write(w, "prop_f64_max_np"); p0().add_prop(e_fl, P::opt_f64("val", vec![Some(f64::MAX)])) .write(w, "prop_f64_max"); p0().add_prop(e_fl, P::f64("val", vec![f64::INFINITY])) .write(w, "prop_f64_pos_inf_np"); p0().add_prop(e_fl, P::opt_f64("val", vec![Some(f64::INFINITY)])) .write(w, "prop_f64_pos_inf"); // Two-feature optional f64 variants geo_varint_with_rle() .geos([P0, P0]) .add_prop( e_fl, P::opt_f64("val", vec![Some(std::f64::consts::PI), None]), ) .write(w, "prop_f64_val_null"); geo_varint_with_rle() .geos([P0, P0]) .add_prop( e_fl, P::opt_f64("val", vec![None, Some(std::f64::consts::PI)]), ) .write(w, "prop_f64_null_val"); let e_str = E::varint(); p0().add_prop(e_str, P::str("val", [""])) .write(w, "prop_str_empty_np"); p0().add_prop(e_str, P::opt_str("val", [Some("")])) .write(w, "prop_str_empty"); p0().add_prop(e_str, P::str("val", ["42"])) .write(w, "prop_str_ascii_np"); p0().add_prop(e_str, P::opt_str("val", [Some("42")])) .write(w, "prop_str_ascii"); p0().add_prop(e_str, P::str("val", ["Line1\n\t\"quoted\"\\path"])) .write(w, "prop_str_escape_np"); p0().add_prop( e_str, P::opt_str("val", [Some("Line1\n\t\"quoted\"\\path")]), ) .write(w, "prop_str_escape"); p0().add_prop(e_str, P::str("val", ["München 📍 cafe\u{0301}"])) .write(w, "prop_str_unicode_np"); p0().add_prop(e_str, P::opt_str("val", [Some("München 📍 cafe\u{0301}")])) .write(w, "prop_str_unicode"); p0().add_prop(e_str, P::str("val", ["hello\u{0000} world\n"])) .write(w, "prop_str_special_np"); p0().add_prop(e_str, P::opt_str("val", [Some("hello\u{0000} world\n")])) .write(w, "prop_str_special"); // Two-feature optional str variants geo_varint_with_rle() .geos([P0, P0]) .add_prop(e_str, P::opt_str("val", [Some("42"), None])) .write(w, "prop_str_val_null"); geo_varint_with_rle() .geos([P0, P0]) .add_prop(e_str, P::opt_str("val", [None, Some("42")])) .write(w, "prop_str_null_val"); geo_varint_with_rle() .geos([P0, P0]) .add_prop(e_str, P::opt_str("val", [Some(""), None])) .write(w, "prop_str_val_empty"); geo_varint_with_rle() .geos([P0, P0]) .add_prop(e_str, P::opt_str("val", [None, Some("")])) .write(w, "prop_str_empty_val"); p0().add_prop(E::varint(), P::bool("active", vec![true])) .add_prop(E::varint(), P::u64("biggest", vec![0])) // FIXME: this should be u64, but java does it this way .add_prop(E::varint(), P::i32("bignum", vec![42])) .add_prop(E::varint(), P::i32("count", vec![42])) .add_prop(E::varint(), P::u32("medium", vec![100])) .add_prop(E::varint(), P::str("name", ["Test Point"])) .add_prop(E::varint(), P::f64("precision", vec![0.123_456_789])) .add_prop(E::varint(), P::f32("temp", vec![25.5])) .write(w, "props_mixed_np"); p0().add_prop(E::varint(), P::opt_bool("active", vec![Some(true)])) .add_prop(E::varint(), P::opt_u64("biggest", vec![Some(0)])) .add_prop(E::varint(), P::opt_i32("bignum", vec![Some(42)])) .add_prop(E::varint(), P::opt_i32("count", vec![Some(42)])) .add_prop(E::varint(), P::opt_u32("medium", vec![Some(100)])) .add_prop(E::varint(), P::opt_str("name", [Some("Test Point")])) .add_prop( E::varint(), P::opt_f64("precision", vec![Some(0.123_456_789)]), ) .add_prop(E::varint(), P::opt_f32("temp", vec![Some(25.5)])) .write(w, "props_mixed"); generate_props_i32(w); generate_props_u32(w); generate_props_u64(w); generate_props_str(w); generate_shared_dictionaries(w); } fn generate_props_i32(w: &mut SynthWriter) { let four_points = || geo_varint_with_rle().geos([P0, P1, P2, P3]); let values = || P::i32("val", vec![42, 42, 42, 42]); let opt_values = || P::opt_i32("val", vec![Some(42), Some(42), Some(42), Some(42)]); four_points() .add_prop(E::varint(), values()) .write(w, "props_i32_np"); four_points() .add_prop(E::varint(), opt_values()) .write(w, "props_i32"); four_points() .add_prop(E::delta_varint(), values()) .write(w, "props_i32_delta_np"); four_points() .add_prop(E::delta_varint(), opt_values()) .write(w, "props_i32_delta"); four_points() .add_prop(E::rle_varint(), values()) .write(w, "props_i32_rle_np"); four_points() .add_prop(E::rle_varint(), opt_values()) .write(w, "props_i32_rle"); four_points() .add_prop(E::delta_rle_varint(), values()) .write(w, "props_i32_delta_rle_np"); four_points() .add_prop(E::delta_rle_varint(), opt_values()) .write(w, "props_i32_delta_rle"); } fn generate_props_u32(w: &mut SynthWriter) { let four_points = || geo_varint_with_rle().geos([P0, P1, P2, P3]); let values = || P::u32("val", vec![9_000, 9_000, 9_000, 9_000]); let opt_values = || { P::opt_u32( "val", vec![Some(9_000), Some(9_000), Some(9_000), Some(9_000)], ) }; four_points() .add_prop(E::varint(), values()) .write(w, "props_u32_np"); four_points() .add_prop(E::varint(), opt_values()) .write(w, "props_u32"); four_points() .add_prop(E::delta_varint(), values()) .write(w, "props_u32_delta_np"); four_points() .add_prop(E::delta_varint(), opt_values()) .write(w, "props_u32_delta"); four_points() .add_prop(E::rle_varint(), values()) .write(w, "props_u32_rle_np"); four_points() .add_prop(E::rle_varint(), opt_values()) .write(w, "props_u32_rle"); four_points() .add_prop(E::delta_rle_varint(), values()) .write(w, "props_u32_delta_rle_np"); four_points() .add_prop(E::delta_rle_varint(), opt_values()) .write(w, "props_u32_delta_rle"); for multiplier in [1, 2, 3, 4] { for offset in [-1, 0, 1] { let count = usize::try_from(128 * multiplier + offset).unwrap(); // Sequence 0,1,2, 0,1,2, 0,1,2, ... let vals: Vec = (0..count).map(|i| u32::try_from(i % 3).unwrap()).collect(); let opt_vals: Vec> = vals.iter().map(|&v| Some(v)).collect(); geo_fastpfor() .meta(E::rle_fastpfor()) .geos(vec![P0; count]) .add_prop(E::fastpfor(), P::u32("val", vals)) .write(w, format!("props_u32_fpf_{count}_np")); geo_fastpfor() .meta(E::rle_fastpfor()) .geos(vec![P0; count]) .add_prop(E::fastpfor(), P::opt_u32("val", opt_vals)) .write(w, format!("props_u32_fpf_{count}")); } } } fn generate_props_u64(w: &mut SynthWriter) { let four_points = || geo_varint_with_rle().geos([P0, P1, P2, P3]); let property = || P::u64("val", vec![9_000, 9_000, 9_000, 9_000]); let opt_property = || { P::opt_u64( "val", vec![Some(9_000), Some(9_000), Some(9_000), Some(9_000)], ) }; four_points() .add_prop(E::varint(), property()) .write(w, "props_u64_np"); four_points() .add_prop(E::varint(), opt_property()) .write(w, "props_u64"); four_points() .add_prop(E::delta_varint(), property()) .write(w, "props_u64_delta_np"); four_points() .add_prop(E::delta_varint(), opt_property()) .write(w, "props_u64_delta"); four_points() .add_prop(E::rle_varint(), property()) .write(w, "props_u64_rle_np"); four_points() .add_prop(E::rle_varint(), opt_property()) .write(w, "props_u64_rle"); four_points() .add_prop(E::delta_rle_varint(), property()) .write(w, "props_u64_delta_rle_np"); four_points() .add_prop(E::delta_rle_varint(), opt_property()) .write(w, "props_u64_delta_rle"); } fn generate_props_str(w: &mut SynthWriter) { let six_points = || geo_varint_with_rle().geos([P1, P2, P3, PH1, PH2, PH3]); let str_vals = [ "residential_zone_north_sector_1", "commercial_zone_south_sector_2", "industrial_zone_east_sector_3", "park_zone_west_sector_4", "water_zone_north_sector_5", "residential_zone_south_sector_6", ]; // _np variant: non-optional (CT::Str, no presence stream) six_points() .add_prop(E::varint(), P::str("val", str_vals)) .write(w, "props_str_np"); // canonical: all-present optional (CT::OptStr + presence), matching Java's format six_points() .add_prop(E::varint(), P::opt_str("val", str_vals.map(Some))) .write(w, "props_str"); // FSST variants — same split six_points() .add_prop_str_fsst(E::varint(), E::varint(), P::str("val", str_vals)) .write(w, "props_str_fsst_np"); six_points() .add_prop_str_fsst( E::varint(), E::varint(), P::opt_str("val", str_vals.map(Some)), ) .write(w, "props_str_fsst"); // FSST compression output is not byte-for-byte consistent with Java's // Two features with the same 30-char value → deduplicated dictionary encoding. // 30 chars because otherwise FSST is skipped. let long_string = || "A".repeat(30); let two_pts = || geo_varint_with_rle().geos([P1, P2]); two_pts() .add_prop_str_dict( E::varint(), E::rle_varint(), P::str("val", [long_string(), long_string()]), ) .write(w, "props_offset_str_np"); two_pts() .add_prop_str_dict( E::varint(), E::rle_varint(), P::opt_str("val", [Some(long_string()), Some(long_string())]), ) .write(w, "props_offset_str"); two_pts() .add_prop_str_fsst_dict( E::varint(), E::varint(), E::rle_varint(), P::str("val", [long_string(), long_string()]), ) .write(w, "props_offset_str_fsst_np"); two_pts() .add_prop_str_fsst_dict( E::varint(), E::varint(), E::rle_varint(), P::opt_str("val", [Some(long_string()), Some(long_string())]), ) .write(w, "props_offset_str_fsst"); } fn generate_shared_dictionaries(w: &mut SynthWriter) { let long_string = || "A".repeat(30); let e_str = E::varint(); p0().add_prop(e_str, P::str("name:de", [long_string()])) .add_prop(e_str, P::str("name:en", [long_string()])) .write(w, "props_no_shared_dict_np"); p0().add_prop(e_str, P::opt_str("name:de", [Some(long_string())])) .add_prop(e_str, P::opt_str("name:en", [Some(long_string())])) .write(w, "props_no_shared_dict"); p0().add_shared_dict( SharedDict::new("name:", StrEncoding::Plain) .col("de", E::varint(), [long_string()]) .col("en", E::varint(), [long_string()]), ) .write(w, "props_shared_dict_np"); p0().add_shared_dict( SharedDict::new("name:", StrEncoding::Plain) .opt("de", E::varint(), [Some(long_string())]) .opt("en", E::varint(), [Some(long_string())]), ) .write(w, "props_shared_dict"); p0().add_shared_dict( SharedDict::new("", StrEncoding::Plain) .col("a", E::varint(), [long_string()]) .col("b", E::varint(), [long_string()]), ) .write(w, "props_shared_dict_no_struct_name_np"); p0().add_shared_dict( SharedDict::new("", StrEncoding::Plain) .opt("a", E::varint(), [Some(long_string())]) .opt("b", E::varint(), [Some(long_string())]), ) .write(w, "props_shared_dict_no_struct_name"); p0().add_shared_dict( SharedDict::new("", StrEncoding::Fsst) .col("a", E::varint(), [long_string()]) .col("b", E::varint(), [long_string()]), ) .write(w, "props_shared_dict_no_struct_name_fsst_np"); p0().add_shared_dict( SharedDict::new("", StrEncoding::Fsst) .opt("a", E::varint(), [Some(long_string())]) .opt("b", E::varint(), [Some(long_string())]), ) .write(w, "props_shared_dict_no_struct_name_fsst"); p0().add_prop(e_str, P::str("place", [long_string()])) .add_shared_dict(SharedDict::new("name:en", StrEncoding::Plain).col( "", E::varint(), [long_string()], )) .write(w, "props_shared_dict_one_child_np"); p0().add_prop(e_str, P::opt_str("place", [Some(long_string())])) .add_shared_dict(SharedDict::new("name:en", StrEncoding::Plain).opt( "", E::varint(), [Some(long_string())], )) .write(w, "props_shared_dict_one_child"); p0().add_shared_dict(SharedDict::new("a", StrEncoding::Plain).col( "", E::varint(), [long_string()], )) .write(w, "props_shared_dict_no_child_name_np"); p0().add_shared_dict(SharedDict::new("a", StrEncoding::Plain).opt( "", E::varint(), [Some(long_string())], )) .write(w, "props_shared_dict_no_child_name"); p0().add_shared_dict( SharedDict::new("name:", StrEncoding::Fsst) .col("de", E::varint(), [long_string()]) .col("en", E::varint(), [long_string()]), ) .write(w, "props_shared_dict_fsst_np"); p0().add_shared_dict( SharedDict::new("name:", StrEncoding::Fsst) .opt("de", E::varint(), [Some(long_string())]) .opt("en", E::varint(), [Some(long_string())]), ) .write(w, "props_shared_dict_fsst"); p0().add_shared_dict(SharedDict::new("a", StrEncoding::Fsst).col( "", E::varint(), [long_string()], )) .write(w, "props_shared_dict_no_child_name_fsst_np"); p0().add_shared_dict(SharedDict::new("a", StrEncoding::Fsst).opt( "", E::varint(), [Some(long_string())], )) .write(w, "props_shared_dict_no_child_name_fsst"); p0().add_prop(e_str, P::str("place", [long_string()])) .add_shared_dict(SharedDict::new("name:en", StrEncoding::Fsst).col( "", E::varint(), [long_string()], )) .write(w, "props_shared_dict_one_child_fsst_np"); p0().add_prop(e_str, P::opt_str("place", [Some(long_string())])) .add_shared_dict(SharedDict::new("name:en", StrEncoding::Fsst).opt( "", E::varint(), [Some(long_string())], )) .write(w, "props_shared_dict_one_child_fsst"); p0() // column names MUST be unique, but the shared dict prefix can duplicate // Note that Java sorts column names for some reason .add_shared_dict( SharedDict::new("name", StrEncoding::Plain) .col(":de", E::varint(), [long_string()]) .col("_en", E::varint(), [long_string()]), ) .add_shared_dict( SharedDict::new("name", StrEncoding::Plain) .col(":he", E::varint(), [long_string()]) .col("_fr", E::varint(), [long_string()]), ) .write(w, "props_shared_dict_2_same_prefix_np"); p0().add_shared_dict( SharedDict::new("name", StrEncoding::Plain) .opt(":de", E::varint(), [Some(long_string())]) .opt("_en", E::varint(), [Some(long_string())]), ) .add_shared_dict( SharedDict::new("name", StrEncoding::Plain) .opt(":he", E::varint(), [Some(long_string())]) .opt("_fr", E::varint(), [Some(long_string())]), ) .write(w, "props_shared_dict_2_same_prefix"); let mixed = || [Some(long_string()), None, Some(long_string())]; let all = || [long_string(), long_string(), long_string()]; let all_opt = || all().map(Some); let none = || [None::, None::, None::]; geo_varint_with_rle() .geos([P0, P0, P0]) .add_shared_dict( SharedDict::new("1-", StrEncoding::Plain) .col("a", E::varint(), all()) .col("b", E::varint(), all()), ) .add_shared_dict( SharedDict::new("2-", StrEncoding::Plain) .col("a", E::varint(), all()) .opt("b", E::varint(), mixed()), ) .add_shared_dict( SharedDict::new("3-", StrEncoding::Plain) .col("a", E::varint(), all()) .opt("b", E::varint(), none()), ) .add_shared_dict( SharedDict::new("4-", StrEncoding::Plain) .opt("a", E::varint(), mixed()) .col("b", E::varint(), all()), ) .add_shared_dict( SharedDict::new("5-", StrEncoding::Plain) .opt("a", E::varint(), mixed()) .opt("b", E::varint(), mixed()), ) .add_shared_dict( SharedDict::new("6-", StrEncoding::Plain) .opt("a", E::varint(), mixed()) .opt("b", E::varint(), none()), ) .add_shared_dict( SharedDict::new("7-", StrEncoding::Plain) .opt("a", E::varint(), none()) .col("b", E::varint(), all()), ) .add_shared_dict( SharedDict::new("8-", StrEncoding::Plain) .opt("a", E::varint(), none()) .opt("b", E::varint(), mixed()), ) .write(w, "props_shared_dict_presence_variants_np"); // canonical: all columns are optional (presence stream always written). geo_varint_with_rle() .geos([P0, P0, P0]) .add_shared_dict( SharedDict::new("1-", StrEncoding::Plain) .opt("a", E::varint(), all_opt()) .opt("b", E::varint(), all_opt()), ) .add_shared_dict( SharedDict::new("2-", StrEncoding::Plain) .opt("a", E::varint(), all_opt()) .opt("b", E::varint(), mixed()), ) .add_shared_dict( SharedDict::new("3-", StrEncoding::Plain) .opt("a", E::varint(), all_opt()) .opt("b", E::varint(), none()), ) .add_shared_dict( SharedDict::new("4-", StrEncoding::Plain) .opt("a", E::varint(), mixed()) .opt("b", E::varint(), all_opt()), ) .add_shared_dict( SharedDict::new("5-", StrEncoding::Plain) .opt("a", E::varint(), mixed()) .opt("b", E::varint(), mixed()), ) .add_shared_dict( SharedDict::new("6-", StrEncoding::Plain) .opt("a", E::varint(), mixed()) .opt("b", E::varint(), none()), ) .add_shared_dict( SharedDict::new("7-", StrEncoding::Plain) .opt("a", E::varint(), none()) .opt("b", E::varint(), all_opt()), ) .add_shared_dict( SharedDict::new("8-", StrEncoding::Plain) .opt("a", E::varint(), none()) .opt("b", E::varint(), mixed()), ) .write(w, "props_shared_dict_presence_variants"); } ================================================ FILE: rust/mlt-synthetics/src/writer.rs ================================================ use std::collections::HashSet; use std::fs; use std::io::Write as _; use std::path::{Path, PathBuf}; use std::str::FromStr as _; use mlt_core::geojson::FeatureCollection; use mlt_core::{Decoder, MltError, Parser}; use crate::Args; use crate::layer::Layer; pub struct SynthWriter { ref_dir: PathBuf, out_dir: PathBuf, verbose: bool, generated: HashSet, rust_written: usize, notes: usize, pub failures: usize, } pub type SynthResult = Result; #[derive(Debug, thiserror::Error)] pub enum SynthErr { #[error(transparent)] Mlt(#[from] MltError), #[error("cannot read reference MLT file: {0}")] ReadRefMlt(#[source] std::io::Error), #[error("MLT mismatch: reference file {} does not match generated content. Content saved to -rust dir.", .0.display())] MltMismatch(PathBuf), #[error("cannot read reference JSON file: {0}")] ReadRefJson(#[source] std::io::Error), #[error("decoded JSON differs from reference")] JsonMismatch, #[error("cannot parse reference as FeatureCollection: {0}")] UnparsableRef(serde_json::Error), #[error("cannot compare FeatureCollections: {0}")] CannotCompare(serde_json::Error), #[error("cannot serialize FeatureCollection: {0}")] SerializeJson(serde_json::Error), #[error("cannot write {0}: {1}")] WriteFile(PathBuf, #[source] std::io::Error), } /// Compare `actual` against the JSON reference file at `ref_path`. /// Returns `Ok(())` on match, or a typed `SynthError` on I/O error, parse failure, or mismatch. pub fn check_json(actual: &FeatureCollection, ref_path: &Path) -> SynthResult<()> { let ref_json = fs::read_to_string(ref_path).map_err(SynthErr::ReadRefJson)?; let expected = FeatureCollection::from_str(&ref_json).map_err(SynthErr::UnparsableRef)?; if actual.equals(&expected).map_err(SynthErr::CannotCompare)? { Ok(()) } else { Err(SynthErr::JsonMismatch) } } pub fn write_file(path: &Path, data: &[u8]) -> SynthResult<()> { Layer::open_new(path) .and_then(|mut f| f.write_all(data)) .map_err(|source| SynthErr::WriteFile(path.to_path_buf(), source)) } pub fn decode_to_json(bytes: &[u8]) -> FeatureCollection { let mut dec = Decoder::default(); let decoded = dec .decode_all(Parser::default().parse_layers(bytes).unwrap()) .unwrap(); FeatureCollection::from_layers(decoded).unwrap() } impl SynthWriter { pub fn new(mut args: Args) -> Self { let canonical_synth = args.synthetics.canonicalize(); let canonical_synth = canonical_synth.unwrap_or_else(|e| { panic!( "reference synthetics dir not found: {}\n{e}", args.synthetics.display() ) }); args.synthetics = canonical_synth; println!("Verifying synthetics against {}", args.synthetics.display()); println!( "Writing rust-only files to {}", args.synthetics_rust.display() ); fs::create_dir_all(&args.synthetics_rust) .unwrap_or_else(|e| panic!("cannot create {}: {e}", args.synthetics_rust.display())); Self { ref_dir: args.synthetics, out_dir: args.synthetics_rust, verbose: args.verbose, failures: 0, generated: HashSet::new(), rust_written: 0, notes: 0, } } pub fn print_note(&mut self, msg: &str) { self.notes += 1; eprintln!("Note: {msg}"); } /// Encode and write (or verify) `layer`, recording the outcome in this writer's statistics. pub fn write(&mut self, layer: Layer, name: impl AsRef) { let name = name.as_ref(); let res = self.write_int(layer, name); match res { Ok(is_rust) => { let typ = if is_rust { self.rust_written += 1; // Record the base name so report_ungenerated won't warn about // ref files that are covered by a rust-only counterpart. if let Some(base) = name.strip_suffix("-rust") { self.generated.insert(base.to_string()); } "wrote" } else { assert!( self.generated.insert(name.to_string()), "duplicate generated name: {name}" ); "ok" }; if self.verbose { println!("{typ:5} {name}"); } } Err(e) => { eprintln!("FAIL {name}: {e}"); self.failures += 1; } } } /// Encode `layer` and either verify (shared files) or write (rust-only files). /// /// Returns `Ok(true)` for a rust-only file, `Ok(false)` for a shared file, /// or `Err` on any failure. fn write_int(&mut self, layer: Layer, mut name: &str) -> SynthResult { let mut is_rust_specific = false; if let Some(base) = name.strip_suffix("-rust") { is_rust_specific = true; name = base; } if name.contains("_fsst") { // FSST frequently generates binary-different but compatible data is_rust_specific = true; } let name_mlt = format!("{name}.mlt"); let name_json = format!("{name}.json"); let rust_mlt = self.out_dir.join(&name_mlt); let rust_json = self.out_dir.join(&name_json); let ref_mlt = self.ref_dir.join(&name_mlt); let ref_json = self.ref_dir.join(&name_json); let ref_json_exists = ref_json.is_file(); let bytes = layer.encode_to_bytes()?; let decoded = decode_to_json(&bytes); if is_rust_specific || !ref_json_exists { // rust-only: write MLT to disk, compare decoded JSON to reference (if it exists). write_file(&rust_mlt, &bytes)?; if ref_json_exists { check_json(&decoded, &ref_json)?; } else { self.print_note(&format!( "Java synthetics doesn't have MLT matching 0x01-rust/{name_mlt}" )); } let mut s = serde_json::to_string_pretty(&decoded).map_err(SynthErr::SerializeJson)?; s.push('\n'); write_file(&rust_json, s.as_bytes())?; Ok(true) } else { // shared: verify bytes and JSON against reference, nothing written to disk. fs::read(&ref_mlt) .map_err(SynthErr::ReadRefMlt) .and_then(|ref_bytes| { if ref_bytes == bytes { Ok(()) } else { write_file(&rust_mlt, &bytes)?; Err(SynthErr::MltMismatch(ref_mlt)) } })?; check_json(&decoded, &ref_json)?; Ok(false) } } /// Warn about `.mlt` files in the reference dir that Rust never generated. /// Prints a summary that includes the total failure count. pub fn report_ungenerated(&mut self) { let mut ref_mlts: Vec = fs::read_dir(&self.ref_dir) .unwrap_or_else(|e| panic!("cannot read {}: {e}", self.ref_dir.display())) .flatten() .filter_map(|e| { let p = e.path(); (p.extension()? == "mlt") .then(|| p.file_stem().unwrap().to_string_lossy().into_owned()) }) .collect(); ref_mlts.sort(); for name in &ref_mlts { if !self.generated.contains(name) { self.print_note(&format!( "Rust synthetics did not generate a test matching Java's 0x01/{name}.mlt" )); } } println!( "Verified: {} | Rust-only: {} | Notes: {} | Failures: {}", self.generated.len(), self.rust_written, self.notes, self.failures, ); } } ================================================ FILE: rust/mlt-wasm/.gitignore ================================================ # wasm-pack output pkg/ # TypeScript compiler output dist/ # npm node_modules/ ================================================ FILE: rust/mlt-wasm/.nvmrc ================================================ 24.11 ================================================ FILE: rust/mlt-wasm/CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [0.1.6](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-wasm-v0.1.5...rust-mlt-wasm-v0.1.6) - 2026-04-29 ### Other - *(rust)* rm "01" from TileLayer01, StagedLayer01 ([#1345](https://github.com/maplibre/maplibre-tile-spec/pull/1345)) ## [0.1.5](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-wasm-v0.1.4...rust-mlt-wasm-v0.1.5) - 2026-04-18 ### Fixed - *(rust)* geo builds on wasm, remove unnecessary feature gates ([#1297](https://github.com/maplibre/maplibre-tile-spec/pull/1297)) ### Other - *(rust)* rm RawStreamData and EncodedStreamData ([#1309](https://github.com/maplibre/maplibre-tile-spec/pull/1309)) - *(rust)* Coord32 cleanup, dep update ([#1304](https://github.com/maplibre/maplibre-tile-spec/pull/1304)) - *(rust)* update geo, simplify tessellation ([#1305](https://github.com/maplibre/maplibre-tile-spec/pull/1305)) - *(java)* Extend synthetic tests to include rings ([#1292](https://github.com/maplibre/maplibre-tile-spec/pull/1292)) - Add offline docs.rs-style workspace docs check to Rust CI and fix surfaced rustdoc links ([#1295](https://github.com/maplibre/maplibre-tile-spec/pull/1295)) ## [0.1.4](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-wasm-v0.1.3...rust-mlt-wasm-v0.1.4) - 2026-04-13 ### Other - *(rust)* move frames/v01 -> decoder, adj use ([#1246](https://github.com/maplibre/maplibre-tile-spec/pull/1246)) - *(rust)* mv tessellation to core ([#1220](https://github.com/maplibre/maplibre-tile-spec/pull/1220)) - more tessellated synthetics ([#1218](https://github.com/maplibre/maplibre-tile-spec/pull/1218)) - *(rust)* implement feature/property iterator and more type state ([#1198](https://github.com/maplibre/maplibre-tile-spec/pull/1198)) ## [0.1.3](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-wasm-v0.1.2...rust-mlt-wasm-v0.1.3) - 2026-03-23 ### Other - *(rust)* migrate to Rust fastpfor ([#1190](https://github.com/maplibre/maplibre-tile-spec/pull/1190)) ## [0.1.2](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-wasm-v0.1.1...rust-mlt-wasm-v0.1.2) - 2026-03-17 ### Other - *(rust)* memory budgeting, codecs ([#1168](https://github.com/maplibre/maplibre-tile-spec/pull/1168)) - *(rust)* introduce EncDec decode states ([#1166](https://github.com/maplibre/maplibre-tile-spec/pull/1166)) - *(rust)* add stateful decoder ([#1163](https://github.com/maplibre/maplibre-tile-spec/pull/1163)) - *(rust)* rename to IdValues and GeometryValues ([#1159](https://github.com/maplibre/maplibre-tile-spec/pull/1159)) - *(rust)* mv impls out of models, use full wire round-trips ([#1158](https://github.com/maplibre/maplibre-tile-spec/pull/1158)) - *(rust)* rework WASM code to use TileLayer ([#1153](https://github.com/maplibre/maplibre-tile-spec/pull/1153)) - *(rust)* remove unnecessary to_owned calls ([#1151](https://github.com/maplibre/maplibre-tile-spec/pull/1151)) - *(rust)* introduce staging types in Rust layer implementation ([#1149](https://github.com/maplibre/maplibre-tile-spec/pull/1149)) - *(rust)* introduce staging types ([#1148](https://github.com/maplibre/maplibre-tile-spec/pull/1148)) - *(rust)* refactor parsing and encoding code ([#1144](https://github.com/maplibre/maplibre-tile-spec/pull/1144)) - *(rust)* get rid of borrowme, add EncDec enum ([#1141](https://github.com/maplibre/maplibre-tile-spec/pull/1141)) - *(rust)* simplify ID model ([#1139](https://github.com/maplibre/maplibre-tile-spec/pull/1139)) - *(rust)* make wasm bench more stable ([#1120](https://github.com/maplibre/maplibre-tile-spec/pull/1120)) - *(rust)* move name into DecodedStrings ([#1108](https://github.com/maplibre/maplibre-tile-spec/pull/1108)) ## [0.1.1](https://github.com/maplibre/maplibre-tile-spec/compare/rust-mlt-wasm-v0.1.0...rust-mlt-wasm-v0.1.1) - 2026-03-10 ### Other - updated the following local packages: mlt-core ================================================ FILE: rust/mlt-wasm/Cargo.toml ================================================ [package] name = "mlt-wasm" description = "WebAssembly bindings for the MapLibre Tile (MLT) format" version = "0.1.6" authors.workspace = true categories.workspace = true edition.workspace = true homepage.workspace = true keywords.workspace = true license.workspace = true repository.workspace = true rust-version.workspace = true [package.metadata.wasm-pack.profile.release] wasm-opt = ["-O3", "--enable-simd", "--enable-bulk-memory"] [lib] crate-type = ["cdylib"] [dependencies] js-sys.workspace = true mlt-core.workspace = true wasm-bindgen.workspace = true [lints] workspace = true ================================================ FILE: rust/mlt-wasm/README.md ================================================ # mlt-wasm WebAssembly bindings for the [MapLibre Tile (MLT)](https://github.com/maplibre/maplibre-tile-spec) decoder. Compiles `mlt-core` to WASM via [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) and ships a TypeScript wrapper exposing a `VectorTileLike` API. ## Layout ``` mlt-wasm/ ├── src/lib.rs # wasm-bindgen bindings ├── js/ │ ├── index.ts # package entry point │ └── vectorTile.ts # VectorTileLike wrapper ├── pkg/ # wasm-pack output (gitignored) ├── dist/ # tsc output (gitignored) ├── Cargo.toml ├── package.json └── tsconfig.json ``` ## Build Requires [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/) and Node.js. ```sh npm run build ``` This runs `wasm-pack build --target bundler --out-dir pkg` followed by `tsc`. Both steps are also available individually: ```sh npm run build:wasm npm run build:ts ``` ## Usage ```ts import { decodeTile } from '@maplibre/mlt-wasm'; const data = new Uint8Array(await fetch(tileUrl).then(r => r.arrayBuffer())); const tile = decodeTile(data); for (const [name, layer] of Object.entries(tile.layers)) { for (let i = 0; i < layer.length; i++) { const feature = layer.feature(i); console.log(feature.type); // 1 | 2 | 3 console.log(feature.id); // number | undefined console.log(feature.properties); // fetched lazily from WASM console.log(feature.loadGeometry()); // Point[][] } } ``` ================================================ FILE: rust/mlt-wasm/js/decoder.bench.ts ================================================ import { readdirSync, readFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import type { VectorTileLike } from "@maplibre/vt-pbf"; import { beforeAll, bench, describe } from "vitest"; import tsDecodeTile from "../../ts/src/mltDecoder"; import type FeatureTable from "../../ts/src/vector/featureTable"; import { decodeTile as wasmDecodeTile } from "./vectorTile"; const __dirname = dirname(fileURLToPath(import.meta.url)); const OMT = resolve(__dirname, "../../../test/expected/tag0x01/omt"); function loadPool(zoom: number): Uint8Array[] { return readdirSync(OMT) .filter((f) => f.startsWith(`${zoom}_`) && f.endsWith(".mlt")) .sort() .map((f) => new Uint8Array(readFileSync(resolve(OMT, f)))); } // zoom 11: 12 tiles, 39–89 KB each (~760 KB total — exceeds typical L2) // zoom 10: 12 tiles, 70–128 KB each (~1.1 MB total) // zoom 14: 10 tiles, 344–763 KB each (~5.5 MB total — exceeds most L3s) const POOLS = [ { label: "small (zoom 11, 39–89 KB)", pool: loadPool(11) }, { label: "medium (zoom 10, 70–128 KB)", pool: loadPool(10) }, { label: "large (zoom 14, 344–763 KB)", pool: loadPool(14) }, ]; function traverseWasm(tile: VectorTileLike): number { let n = 0; for (const layer of Object.values(tile.layers)) { for (let i = 0; i < layer.length; i++) { const f = layer.feature(i); void f.properties; f.loadGeometry(); n++; } } return n; } function traverseTs(tables: FeatureTable[]): number { let n = 0; for (const table of tables) { const geometries = table.geometryVector.getGeometries(); for (let i = 0; i < table.numFeatures; i++) { void geometries[i]; for (const col of table.propertyVectors) { if (col) col.getValue(i); } n++; } } return n; } // Requires Node to be started with --expose-gc (done by the bench npm script); no-op otherwise. function drainGC(): void { const _gc = (globalThis as { gc?: () => void }).gc; if (typeof _gc === "function") { _gc(); _gc(); } } const OPTIONS = { warmupTime: 500, time: 2000, minSamples: 40, } as const; for (const { label, pool } of POOLS) { describe(`decode + traverse - ${label}`, () => { let ti = 0; let wi = 0; // Drain heap garbage left by the previous describe block so GC does not // fire at an unpredictable point inside this block's measurement window. beforeAll(drainGC); bench( "TS decoder", () => { const tables = tsDecodeTile(pool[ti++ % pool.length]); if (traverseTs(tables) < 0) throw new Error("unreachable"); }, OPTIONS, ); bench( "WASM decoder", () => { const tile = wasmDecodeTile(pool[wi++ % pool.length]); if (traverseWasm(tile) < 0) throw new Error("unreachable"); }, OPTIONS, ); }); } ================================================ FILE: rust/mlt-wasm/js/index.ts ================================================ export type { VectorTileFeatureLike, VectorTileLayerLike, VectorTileLike, } from "@maplibre/vt-pbf"; export { decodeTile } from "./vectorTile"; ================================================ FILE: rust/mlt-wasm/js/synthetic.spec.ts ================================================ import { readFile } from "node:fs/promises"; import type { VectorTileLike } from "@maplibre/vt-pbf"; import { describe, expect, it } from "vitest"; import { compareWithTolerance, getTestCases, writeActualOutput, } from "../../../test/synthetic/synthetic-test-utils"; import { decodeTile, type MltFeature, MltGeometryType, type MltLayer, } from "./vectorTile"; const UNIMPLEMENTED_SYNTHETICS = new Map([ ["poly_collinear_fpf", "FastPFor not supported"], ["poly_collinear_fpf_tes", "FastPFor not supported"], ["poly_fpf", "FastPFor not supported"], ["poly_fpf_tes", "FastPFor not supported"], ["poly_hole_fpf", "FastPFor not supported"], ["poly_hole_fpf_tes", "FastPFor not supported"], ["poly_hole_touching_fpf", "FastPFor not supported"], ["poly_hole_touching_fpf_tes", "FastPFor not supported"], ["poly_multi_fpf", "FastPFor not supported"], ["poly_multi_fpf_tes", "FastPFor not supported"], ["poly_self_intersect_fpf", "FastPFor not supported"], ["poly_self_intersect_fpf_tes", "FastPFor not supported"], ["poly_multi_morton_hole_morton", "Pending investigation"], ]); describe("MLT WASM Decoder - Synthetic tests", () => { expect.addEqualityTesters([compareWithTolerance]); const testCases = getTestCases(Array.from(UNIMPLEMENTED_SYNTHETICS.keys())); for (const { name, content, fileName } of testCases.active) { it(name, async () => { const actual = await decodeMLT(fileName); writeActualOutput(fileName, actual); expect(actual).toEqual(content); }); } for (const skippedTest of testCases.skipped) { it.skip(skippedTest, () => { // Test is skipped since it is not supported yet }); } }); async function decodeMLT( mltFilePath: string, ): Promise> { const mltBuffer = await readFile(mltFilePath); const tile = decodeTile(new Uint8Array(mltBuffer)); return tileToFeatureCollection(tile) as unknown as Record; } function tileToFeatureCollection( tile: VectorTileLike, ): GeoJSON.FeatureCollection { const features: GeoJSON.Feature[] = []; for (const layer of Object.values(tile.layers)) { const mltLayer = layer as MltLayer; for (let i = 0; i < mltLayer.length; i++) { const feature = mltLayer.feature(i); const properties: Record = { _layer: mltLayer.name, _extent: mltLayer.extent, }; for (let k = 0; k < mltLayer.propertyKeys.length; k++) { const key = mltLayer.propertyKeys[k]; const col = mltLayer.propertyColumns[k]; let val = feature.properties[key]; if (typeof val === "number") { if (Number.isNaN(val)) { if (col instanceof Float32Array) val = "f32::NAN"; else if (col instanceof Float64Array) val = "f64::NAN"; } else if (val === Infinity) { if (col instanceof Float32Array) val = "f32::INFINITY"; else if (col instanceof Float64Array) val = "f64::INFINITY"; } else if (val === -Infinity) { if (col instanceof Float32Array) val = "f32::NEG_INFINITY"; else if (col instanceof Float64Array) val = "f64::NEG_INFINITY"; } } if (val !== undefined) { properties[key] = val; } } const geojsonFeature: GeoJSON.Feature = { type: "Feature", geometry: getGeometry(feature), properties, }; if (feature.id !== undefined) { geojsonFeature.id = feature.id; } features.push(geojsonFeature); } } return { type: "FeatureCollection", features }; } function getGeometry(feature: MltFeature): GeoJSON.Geometry { const rings = feature.loadGeometry(); const coords = rings.map((ring) => ring.map((p) => [p.x, p.y])); switch (feature.mltType) { case MltGeometryType.Point: return { type: "Point", coordinates: coords[0][0] }; case MltGeometryType.MultiPoint: return { type: "MultiPoint", coordinates: coords.map((r) => r[0]) }; case MltGeometryType.LineString: return { type: "LineString", coordinates: coords[0] }; case MltGeometryType.MultiLineString: return { type: "MultiLineString", coordinates: coords }; case MltGeometryType.Polygon: { const polygons = feature.loadPolygons(); return { type: "Polygon", coordinates: polygons[0].map((ring) => closeRing(ring.map((p) => [p.x, p.y])), ), }; } case MltGeometryType.MultiPolygon: { const polygons = feature.loadPolygons(); return { type: "MultiPolygon", coordinates: polygons.map((polygon) => polygon.map((ring) => closeRing(ring.map((p) => [p.x, p.y]))), ), }; } default: throw new Error(`Unsupported MLT geometry type: ${feature.mltType}`); } } /** GeoJSON polygons must have their first and last coordinate identical. */ function closeRing(ring: number[][]): number[][] { if (ring.length === 0) return ring; const first = ring[0]; const last = ring[ring.length - 1]; if (first[0] !== last[0] || first[1] !== last[1]) { return [...ring, [first[0], first[1]]]; } return ring; } ================================================ FILE: rust/mlt-wasm/js/vectorTile.ts ================================================ import Point from "@mapbox/point-geometry"; import type { VectorTileFeatureLike, VectorTileLayerLike, VectorTileLike, } from "@maplibre/vt-pbf"; import { decode_tile as wasmDecodeTile } from "../pkg/mlt_wasm.js"; // --------------------------------------------------------------------------- // WASM interface // --------------------------------------------------------------------------- interface LayerGeometry { /** Cumulative offsets into part_offsets (multi-geometry types only). Zero-length otherwise. */ geometry_offsets(): Uint32Array; /** Cumulative offsets into ring_offsets or directly into vertices. Zero-length for pure Point layers. */ part_offsets(): Uint32Array; /** Cumulative vertex-count offsets. Zero-length when no ring-level indirection is needed. */ ring_offsets(): Uint32Array; /** Flat [x0, y0, x1, y1, …] vertex buffer in tile coordinates. */ vertices(): Int32Array; } interface WasmMltTile { layer_count(): number; layer_name(layer_idx: number): string; layer_extent(layer_idx: number): number; feature_count(layer_idx: number): number; /** Bulk MVT geometry types for the whole layer as a Uint8Array (one byte per feature: 1/2/3). */ layer_types(layer_idx: number): Uint8Array; /** Original MLT geometry types (0=Point, 1=LineString, 2=Polygon, 3=MultiPoint, 4=MultiLineString, 5=MultiPolygon). */ layer_mlt_types(layer_idx: number): Uint8Array; /** * Bulk IDs for the whole layer as a Float64Array (one f64 per feature). * NaN when the feature has no ID. */ layer_ids(layer_idx: number): Float64Array; /** * All decoded geometry arrays for the layer in one call. * JS walks these directly — zero WASM calls per feature for geometry. */ layer_geometry(layer_idx: number): LayerGeometry; /** Column names for the layer, parallel to layer_properties(). */ layer_property_keys(layer_idx: number): string[]; /** * All property values as an array of columns, parallel to layer_property_keys(). * Each column is a typed array (numeric) or plain Array (bool/string) of * length feature_count. Index i gives the value for feature i; absent values * are NaN (numeric) or undefined (bool/string). */ layer_properties( layer_idx: number, ): Array< | Int8Array | Uint8Array | Int32Array | Uint32Array | Float32Array | Float64Array | Array >; feature_properties( layer_idx: number, feature_idx: number, ): Record; free(): void; } // --------------------------------------------------------------------------- // Geometry type enums // --------------------------------------------------------------------------- // MVT geometry types (VectorTileFeatureLike.type) const POINT = 1; const LINESTRING = 2; const POLYGON = 3; /** Mirrors `GeometryType` in mlt-core — preserves the single vs multi distinction that MVT collapses. */ export enum MltGeometryType { Point = 0, LineString = 1, Polygon = 2, MultiPoint = 3, MultiLineString = 4, MultiPolygon = 5, } // --------------------------------------------------------------------------- // loadGeometry — JS equivalent of DecodedGeometry::to_mvt_rings // --------------------------------------------------------------------------- function openRing(verts: Int32Array, start: number, end: number): Point[] { const ring: Point[] = new Array(end - start) as Point[]; for (let i = start; i < end; i++) { ring[i - start] = new Point(verts[i * 2], verts[i * 2 + 1]); } return ring; } function loadGeometry( mvtType: number, featureIdx: number, geomOffsets: Uint32Array, partOffsets: Uint32Array, ringOffsets: Uint32Array, verts: Int32Array, ): Point[][] { const hasGeomOffsets = geomOffsets.length > 0; const hasPartOffsets = partOffsets.length > 0; const hasRingOffsets = ringOffsets.length > 0; if (mvtType === POINT) { if (!hasGeomOffsets) { // Mixed-type layers may have part/ring indirection even for points. let idx = featureIdx; if (hasPartOffsets) idx = partOffsets[idx]; if (hasRingOffsets) idx = ringOffsets[idx]; return [[new Point(verts[idx * 2], verts[idx * 2 + 1])]]; } else { const gStart = geomOffsets[featureIdx]; const gEnd = geomOffsets[featureIdx + 1]; const rings: Point[][] = new Array(gEnd - gStart) as Point[][]; for (let g = gStart; g < gEnd; g++) { let idx = g; if (hasPartOffsets) idx = partOffsets[idx]; if (hasRingOffsets) idx = ringOffsets[idx]; rings[g - gStart] = [new Point(verts[idx * 2], verts[idx * 2 + 1])]; } return rings; } } if (mvtType === LINESTRING) { if (!hasGeomOffsets) { let start: number; let end: number; if (hasRingOffsets) { const partIdx = partOffsets[featureIdx]; start = ringOffsets[partIdx]; end = ringOffsets[partIdx + 1]; } else { start = partOffsets[featureIdx]; end = partOffsets[featureIdx + 1]; } return [openRing(verts, start, end)]; } else { const gStart = geomOffsets[featureIdx]; const gEnd = geomOffsets[featureIdx + 1]; const result: Point[][] = new Array(gEnd - gStart) as Point[][]; for (let g = gStart; g < gEnd; g++) { let start: number; let end: number; if (hasRingOffsets) { const partIdx = partOffsets[g]; start = ringOffsets[partIdx]; end = ringOffsets[partIdx + 1]; } else { start = partOffsets[g]; end = partOffsets[g + 1]; } result[g - gStart] = openRing(verts, start, end); } return result; } } if (mvtType === POLYGON) { if (!hasGeomOffsets) { const partStart = partOffsets[featureIdx]; const partEnd = partOffsets[featureIdx + 1]; const rings: Point[][] = new Array(partEnd - partStart) as Point[][]; for (let r = partStart; r < partEnd; r++) { rings[r - partStart] = openRing( verts, ringOffsets[r], ringOffsets[r + 1], ); } return rings; } else { // Flat ring list matching MVT convention — use loadPolygons() for grouped output. const gStart = geomOffsets[featureIdx]; const gEnd = geomOffsets[featureIdx + 1]; const result: Point[][] = []; for (let g = gStart; g < gEnd; g++) { const partStart = partOffsets[g]; const partEnd = partOffsets[g + 1]; for (let r = partStart; r < partEnd; r++) { result.push(openRing(verts, ringOffsets[r], ringOffsets[r + 1])); } } return result; } } return []; } /** Returns rings grouped by polygon using offset arrays instead of winding-order heuristics. */ function loadPolygons( featureIdx: number, geomOffsets: Uint32Array, partOffsets: Uint32Array, ringOffsets: Uint32Array, verts: Int32Array, ): Point[][][] { if (geomOffsets.length === 0) { const partStart = partOffsets[featureIdx]; const partEnd = partOffsets[featureIdx + 1]; const rings: Point[][] = new Array(partEnd - partStart) as Point[][]; for (let r = partStart; r < partEnd; r++) { rings[r - partStart] = openRing( verts, ringOffsets[r], ringOffsets[r + 1], ); } return [rings]; } const gStart = geomOffsets[featureIdx]; const gEnd = geomOffsets[featureIdx + 1]; const polygons: Point[][][] = new Array(gEnd - gStart) as Point[][][]; for (let g = gStart; g < gEnd; g++) { const partStart = partOffsets[g]; const partEnd = partOffsets[g + 1]; const rings: Point[][] = new Array(partEnd - partStart) as Point[][]; for (let r = partStart; r < partEnd; r++) { rings[r - partStart] = openRing( verts, ringOffsets[r], ringOffsets[r + 1], ); } polygons[g - gStart] = rings; } return polygons; } // --------------------------------------------------------------------------- // MltFeature // --------------------------------------------------------------------------- export class MltFeature implements VectorTileFeatureLike { readonly extent: number; private _type: 0 | 1 | 2 | 3 | undefined; private _id: number | undefined | null; constructor( private readonly _featureIdx: number, extent: number, private readonly _types: Uint8Array, private readonly _mltTypes: Uint8Array, private readonly _ids: Float64Array, private readonly _geomOffsets: Uint32Array, private readonly _partOffsets: Uint32Array, private readonly _ringOffsets: Uint32Array, private readonly _verts: Int32Array, private readonly propertyKeys: string[], private readonly propertyColumns: Array< | Int8Array | Uint8Array | Int32Array | Uint32Array | Float32Array | Float64Array | Array >, ) { this.extent = extent; this._id = null; } get mltType(): MltGeometryType { return this._mltTypes[this._featureIdx] as MltGeometryType; } get type(): 0 | 1 | 2 | 3 { if (this._type === undefined) { this._type = this._types[this._featureIdx] as 0 | 1 | 2 | 3; } return this._type; } get id(): number | undefined { if (this._id === null) { const raw = this._ids[this._featureIdx]; this._id = Number.isNaN(raw) ? undefined : raw; } return this._id as number | undefined; } get properties(): Record { const result: Record = {}; for (let k = 0; k < this.propertyKeys.length; k++) { const col = this.propertyColumns[k]; const val = col[this._featureIdx]; if (val !== undefined) { result[this.propertyKeys[k]] = val as number | string | boolean; } } return result; } loadGeometry(): Point[][] { return loadGeometry( this.type, this._featureIdx, this._geomOffsets, this._partOffsets, this._ringOffsets, this._verts, ); } /** Returns rings grouped by polygon — avoids the lossy winding-order heuristic in MVT's classifyRings. */ loadPolygons(): Point[][][] { if (this.type !== POLYGON) return [this.loadGeometry()]; return loadPolygons( this._featureIdx, this._geomOffsets, this._partOffsets, this._ringOffsets, this._verts, ); } } // --------------------------------------------------------------------------- // MltLayer // --------------------------------------------------------------------------- export class MltLayer implements VectorTileLayerLike { readonly version = 1 as const; readonly name: string; readonly extent: number; readonly length: number; private readonly _types: Uint8Array; private readonly _mltTypes: Uint8Array; private readonly _ids: Float64Array; private readonly _geomOffsets: Uint32Array; private readonly _partOffsets: Uint32Array; private readonly _ringOffsets: Uint32Array; private readonly _verts: Int32Array; readonly propertyKeys: string[]; readonly propertyColumns: Array< | Int8Array | Uint8Array | Int32Array | Uint32Array | Float32Array | Float64Array | Array >; constructor( readonly _tile: WasmMltTile, readonly _layerIdx: number, name: string, ) { this.name = name; this.extent = _tile.layer_extent(_layerIdx); this.length = _tile.feature_count(_layerIdx); this._types = _tile.layer_types(_layerIdx); this._mltTypes = _tile.layer_mlt_types(_layerIdx); this._ids = _tile.layer_ids(_layerIdx); const geom = _tile.layer_geometry(_layerIdx); this._geomOffsets = geom.geometry_offsets(); this._partOffsets = geom.part_offsets(); this._ringOffsets = geom.ring_offsets(); this._verts = geom.vertices(); this.propertyKeys = _tile.layer_property_keys(_layerIdx); this.propertyColumns = _tile.layer_properties(_layerIdx); } feature(i: number): MltFeature { return new MltFeature( i, this.extent, this._types, this._mltTypes, this._ids, this._geomOffsets, this._partOffsets, this._ringOffsets, this._verts, this.propertyKeys, this.propertyColumns, ); } } export function decodeTile(data: Uint8Array): VectorTileLike { const tile = wasmDecodeTile(data) as WasmMltTile; const layers: Record = {}; for (let i = 0; i < tile.layer_count(); i++) { const name = tile.layer_name(i); layers[name] = new MltLayer(tile, i, name); } return { layers }; } ================================================ FILE: rust/mlt-wasm/package.json ================================================ { "name": "@maplibre/mlt-wasm", "version": "0.1.6", "description": "WebAssembly-backed MapLibre Tile (MLT) decoder", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ "/dist", "/pkg" ], "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/maplibre/maplibre-tile-spec/#readme", "repository": { "type": "git", "url": "git+https://github.com/maplibre/maplibre-tile-spec" }, "bugs": { "url": "https://github.com/maplibre/maplibre-tile-spec/issues" }, "keywords": [ "maplibre", "mlt", "vector-tile", "wasm", "webassembly", "gis" ], "scripts": { "build:wasm": "RUSTFLAGS=\"-Zunstable-options -Cpanic=immediate-abort -Ctarget-feature=+simd128\" CARGO_UNSTABLE_BUILD_STD=std,panic_abort RUSTUP_TOOLCHAIN=nightly wasm-pack build --target bundler --weak-refs --out-dir pkg", "build:wasm-gz": "gzip --best --no-name pkg/mlt_wasm_bg.wasm --keep --force && du -sh pkg/*.wasm*", "build:ts": "tsc", "build": "npm run build:wasm && npm run build:ts && npm run build:wasm-gz", "test": "vitest --coverage --coverage.reportOnFailure --run", "test:watch": "vitest --coverage --coverage.reportOnFailure --ui", "bench": "node --expose-gc --no-liftoff --no-wasm-tier-up node_modules/.bin/vitest bench" }, "dependencies": { "@mapbox/point-geometry": "1.1.0" }, "devDependencies": { "@maplibre/vt-pbf": "4.3.0", "@types/node": "^24.10.2", "@vitest/coverage-v8": "^4.0.1", "@vitest/ui": "^4.0.18", "typescript": "^5.9.3", "vite-plugin-top-level-await": "^1.6.0", "vite-plugin-wasm": "^3.5.0", "vitest": "^4.0.1" } } ================================================ FILE: rust/mlt-wasm/src/geometry.rs ================================================ use js_sys::{Int32Array, Uint32Array}; use mlt_core::GeometryValues; use wasm_bindgen::prelude::*; /// All decoded geometry arrays for a single layer, fetched in one WASM call. /// /// JS indexes into these typed arrays directly inside `loadGeometry()`, so /// there are **zero** per-feature WASM boundary crossings for geometry. /// /// ## Array semantics (mirrors `GeometryValues`) /// /// All offset arrays are cumulative: `offsets[i]` is the start index and /// `offsets[i+1]` is the exclusive end for feature/part/ring `i`. /// Vertex indices count whole vertices (pairs), so vertex `n` lives at /// `vertices[n*2]`, `vertices[n*2+1]`. /// /// | Getter | Present for | /// |--------------------|-----------------------------------------------------------| /// | `geometry_offsets` | `MultiPoint`, `MultiLineString`, `MultiPolygon` | /// | `part_offsets` | `LineString`, `Polygon`, `MultiLineString`, `MultiPolygon`| /// | `ring_offsets` | `Polygon`, `MultiPolygon` (+ `LineString` when mixed) | /// | `vertices` | always | /// /// Absent offset arrays are returned as zero-length `Uint32Array`s so JS can /// always branch on `.length` without a null-check. #[wasm_bindgen] pub struct LayerGeometry { pub(crate) geometry_offsets: Uint32Array, pub(crate) part_offsets: Uint32Array, pub(crate) ring_offsets: Uint32Array, pub(crate) vertices: Int32Array, } #[wasm_bindgen] impl LayerGeometry { /// Cumulative offsets into `part_offsets` for multi-geometry types. /// Zero-length when no multi-geometry features are present. #[must_use] pub fn geometry_offsets(&self) -> Uint32Array { self.geometry_offsets.clone() } /// Cumulative offsets into `ring_offsets` (or directly into `vertices` /// for `LineString` layers without rings). /// Zero-length for pure Point layers. #[must_use] pub fn part_offsets(&self) -> Uint32Array { self.part_offsets.clone() } /// Cumulative offsets into the vertex buffer (counting whole vertices). /// Zero-length when no ring-level indirection is needed. #[must_use] pub fn ring_offsets(&self) -> Uint32Array { self.ring_offsets.clone() } /// Flat vertex buffer: `[x0, y0, x1, y1, …]` in tile coordinates. #[must_use] pub fn vertices(&self) -> Int32Array { self.vertices.clone() } } impl LayerGeometry { /// Build a [`LayerGeometry`] from a decoded [`GeometryValues`]. pub(crate) fn from_values(geom: &GeometryValues) -> Self { let geometry_offsets = geom .geometry_offsets() .map_or_else(|| Uint32Array::new_with_length(0), Uint32Array::from); let part_offsets = geom .part_offsets() .map_or_else(|| Uint32Array::new_with_length(0), Uint32Array::from); let ring_offsets = geom .ring_offsets() .map_or_else(|| Uint32Array::new_with_length(0), Uint32Array::from); let vertices = geom .vertices() .map_or_else(|| Int32Array::new_with_length(0), Int32Array::from); Self { geometry_offsets, part_offsets, ring_offsets, vertices, } } } ================================================ FILE: rust/mlt-wasm/src/layer.rs ================================================ use mlt_core::{GeometryValues, TileLayer}; /// All per-layer state owned by [`crate::tile::MltTile`]. /// /// Fully decoded at `decode_tile` time — no lazy loading. pub(crate) struct DecodedLayer { pub(crate) tile: TileLayer, /// MVT geometry types (0/1/2/3) — collapses single and multi variants. pub(crate) types_array: js_sys::Uint8Array, /// Original MLT geometry types — preserves the single vs multi distinction. pub(crate) mlt_types_array: js_sys::Uint8Array, pub(crate) geometry: GeometryValues, } ================================================ FILE: rust/mlt-wasm/src/lib.rs ================================================ //! WebAssembly bindings for the `MapLibre` Tile (MLT) format. //! //! # Design //! //! A single `MltTile` struct owns all decoded [`mlt_core::TileLayer`] data for //! every layer in the tile. No per-layer or per-feature WASM objects are //! created; every accessor takes explicit `(layer_idx, feature_idx)` arguments //! so the JavaScript side can keep plain numeric indices rather than //! heap-allocated wrapper objects. //! //! ## Geometry //! //! `MltTile::layer_geometry` returns a `LayerGeometry` //! whose typed-array getters expose the raw offset and vertex buffers. //! JS walks these directly — zero WASM boundary crossings per feature. //! //! ## IDs //! //! `MltTile::layer_ids` returns a `Float64Array` — one `f64` per //! feature. Absent IDs are `NaN` (≡ `undefined` after the JS wrapper checks //! `isNaN`). IDs above `Number.MAX_SAFE_INTEGER` lose precision. //! //! ## Properties //! //! `MltTile::layer_property_keys` and `MltTile::layer_properties` //! expose all property columns as typed arrays built once per layer. JS reads //! any feature's property with a single array index — zero WASM calls during //! traversal. mod geometry; mod layer; mod properties; mod tile; use js_sys::Uint8Array; use layer::DecodedLayer; use mlt_core::{Decoder, GeometryType, MltError, Parser}; use tile::MltTile; use wasm_bindgen::prelude::*; /// Decode a raw MLT tile blob and return an `MltTile`. /// /// All geometry, IDs and properties are decoded eagerly into row-oriented /// [`mlt_core::TileLayer`] values. #[wasm_bindgen] pub fn decode_tile(data: &[u8]) -> Result { let mut parser = Parser::default(); let raw_layers = parser.parse_layers(data).map_err(|e| to_js_err(&e))?; let mut dec = Decoder::default(); let mut layers = Vec::with_capacity(raw_layers.len()); for raw_layer in raw_layers { // Skip non-Tag01 layers. let mlt_core::Layer::Tag01(layer01) = raw_layer else { continue; }; // Decode all columns at once, then extract geometry arrays before consuming into tile. let parsed_layer = layer01.decode_all(&mut dec).map_err(|e| to_js_err(&e))?; // Clone geometry values for building WASM typed arrays (zero wire-decode overhead: // geometry is already in columnar form from decode_all). let parsed_geometry = parsed_layer.geometry_values().clone(); let (types_bytes, mlt_types_bytes): (Vec, Vec) = parsed_geometry .vector_types() .iter() .map(|t| { let mvt = match t { GeometryType::Point | GeometryType::MultiPoint => 1, GeometryType::LineString | GeometryType::MultiLineString => 2, GeometryType::Polygon | GeometryType::MultiPolygon => 3, #[allow(unreachable_patterns)] _ => 0, }; (mvt, *t as u8) }) .unzip(); let types_array = Uint8Array::from(types_bytes.as_slice()); let mlt_types_array = Uint8Array::from(mlt_types_bytes.as_slice()); let tile = parsed_layer .into_tile(&mut dec) .map_err(|e| to_js_err(&e))?; layers.push(DecodedLayer { tile, types_array, mlt_types_array, geometry: parsed_geometry, }); } Ok(MltTile { layers }) } pub(crate) fn to_js_err(e: &MltError) -> JsError { JsError::new(&e.to_string()) } ================================================ FILE: rust/mlt-wasm/src/properties.rs ================================================ use js_sys::{Array, Float64Array, Int8Array, Int32Array, Uint8Array, Uint32Array}; use mlt_core::{PropValue, TileLayer}; use wasm_bindgen::prelude::*; /// Cached bulk-property data for a single layer. pub(crate) struct PropCache { /// One JS string per logical column, parallel to `columns`. pub(crate) keys: Array, /// One typed array (or plain `Array`) per logical column, parallel to `keys`. /// Each array has length `feature_count`; index `i` is the value for feature `i`. pub(crate) columns: Array, } /// Build a [`PropCache`] from a fully decoded [`TileLayer`]. pub(crate) fn build_prop_cache(tile: &TileLayer) -> PropCache { let n = tile.features.len(); let keys = Array::new(); let columns = Array::new(); for (col_idx, name) in tile.property_names.iter().enumerate() { keys.push(&JsValue::from_str(name)); columns.push(&build_column(tile, col_idx, n)); } PropCache { keys, columns } } fn feature_count_u32(n: usize) -> u32 { u32::try_from(n).expect("feature count fits in u32") } fn idx_u32(i: usize) -> u32 { u32::try_from(i).expect("index fits in u32") } #[allow(clippy::cast_precision_loss)] fn build_column(tile: &TileLayer, col_idx: usize, n: usize) -> JsValue { // Peek at the first feature to determine the column variant. let first = tile .features .first() .and_then(|f| f.properties.get(col_idx)); match first { Some(PropValue::Bool(_)) => { let arr = Array::new_with_length(feature_count_u32(n)); for (i, f) in tile.features.iter().enumerate() { if let Some(PropValue::Bool(Some(b))) = f.properties.get(col_idx) { arr.set(idx_u32(i), JsValue::from_bool(*b)); } } arr.into() } Some(PropValue::I8(_)) => { let any_none = tile .features .iter() .any(|f| matches!(f.properties.get(col_idx), Some(PropValue::I8(None)))); if any_none { let arr = Array::new_with_length(feature_count_u32(n)); for (i, f) in tile.features.iter().enumerate() { if let Some(PropValue::I8(Some(v))) = f.properties.get(col_idx) { arr.set(idx_u32(i), JsValue::from_f64(f64::from(*v))); } } arr.into() } else { let buf: Vec = tile .features .iter() .filter_map(|f| { if let Some(PropValue::I8(v)) = f.properties.get(col_idx) { *v } else { None } }) .collect(); Int8Array::from(buf.as_slice()).into() } } Some(PropValue::U8(_)) => { let any_none = tile .features .iter() .any(|f| matches!(f.properties.get(col_idx), Some(PropValue::U8(None)))); if any_none { let arr = Array::new_with_length(feature_count_u32(n)); for (i, f) in tile.features.iter().enumerate() { if let Some(PropValue::U8(Some(v))) = f.properties.get(col_idx) { arr.set(idx_u32(i), JsValue::from_f64(f64::from(*v))); } } arr.into() } else { let buf: Vec = tile .features .iter() .filter_map(|f| { if let Some(PropValue::U8(v)) = f.properties.get(col_idx) { *v } else { None } }) .collect(); Uint8Array::from(buf.as_slice()).into() } } Some(PropValue::I32(_)) => { let any_none = tile .features .iter() .any(|f| matches!(f.properties.get(col_idx), Some(PropValue::I32(None)))); if any_none { let arr = Array::new_with_length(feature_count_u32(n)); for (i, f) in tile.features.iter().enumerate() { if let Some(PropValue::I32(Some(v))) = f.properties.get(col_idx) { arr.set(idx_u32(i), JsValue::from_f64(f64::from(*v))); } } arr.into() } else { let buf: Vec = tile .features .iter() .filter_map(|f| { if let Some(PropValue::I32(v)) = f.properties.get(col_idx) { *v } else { None } }) .collect(); Int32Array::from(buf.as_slice()).into() } } Some(PropValue::U32(_)) => { let any_none = tile .features .iter() .any(|f| matches!(f.properties.get(col_idx), Some(PropValue::U32(None)))); if any_none { let arr = Array::new_with_length(feature_count_u32(n)); for (i, f) in tile.features.iter().enumerate() { if let Some(PropValue::U32(Some(v))) = f.properties.get(col_idx) { arr.set(idx_u32(i), JsValue::from_f64(f64::from(*v))); } } arr.into() } else { let buf: Vec = tile .features .iter() .filter_map(|f| { if let Some(PropValue::U32(v)) = f.properties.get(col_idx) { *v } else { None } }) .collect(); Uint32Array::from(buf.as_slice()).into() } } Some(PropValue::I64(_)) => { let any_none = tile .features .iter() .any(|f| matches!(f.properties.get(col_idx), Some(PropValue::I64(None)))); if any_none { let arr = Array::new_with_length(feature_count_u32(n)); for (i, f) in tile.features.iter().enumerate() { if let Some(PropValue::I64(Some(v))) = f.properties.get(col_idx) { arr.set(idx_u32(i), JsValue::from_f64(*v as f64)); } } arr.into() } else { let buf: Vec = tile .features .iter() .filter_map(|f| { if let Some(PropValue::I64(v)) = f.properties.get(col_idx) { v.map(|n| n as f64) } else { None } }) .collect(); Float64Array::from(buf.as_slice()).into() } } Some(PropValue::U64(_)) => { let any_none = tile .features .iter() .any(|f| matches!(f.properties.get(col_idx), Some(PropValue::U64(None)))); if any_none { let arr = Array::new_with_length(feature_count_u32(n)); for (i, f) in tile.features.iter().enumerate() { if let Some(PropValue::U64(Some(v))) = f.properties.get(col_idx) { arr.set(idx_u32(i), JsValue::from_f64(*v as f64)); } } arr.into() } else { let buf: Vec = tile .features .iter() .filter_map(|f| { if let Some(PropValue::U64(v)) = f.properties.get(col_idx) { v.map(|n| n as f64) } else { None } }) .collect(); Float64Array::from(buf.as_slice()).into() } } Some(PropValue::F32(_)) => { let any_none = tile .features .iter() .any(|f| matches!(f.properties.get(col_idx), Some(PropValue::F32(None)))); if any_none { let arr = Array::new_with_length(feature_count_u32(n)); for (i, f) in tile.features.iter().enumerate() { if let Some(PropValue::F32(Some(v))) = f.properties.get(col_idx) { arr.set(idx_u32(i), JsValue::from_f64(f64::from(*v))); } } arr.into() } else { let buf: Vec = tile .features .iter() .filter_map(|f| { if let Some(PropValue::F32(v)) = f.properties.get(col_idx) { *v } else { None } }) .collect(); js_sys::Float32Array::from(buf.as_slice()).into() } } Some(PropValue::F64(_)) => { let any_none = tile .features .iter() .any(|f| matches!(f.properties.get(col_idx), Some(PropValue::F64(None)))); if any_none { let arr = Array::new_with_length(feature_count_u32(n)); for (i, f) in tile.features.iter().enumerate() { if let Some(PropValue::F64(Some(v))) = f.properties.get(col_idx) { arr.set(idx_u32(i), JsValue::from_f64(*v)); } } arr.into() } else { let buf: Vec = tile .features .iter() .filter_map(|f| { if let Some(PropValue::F64(v)) = f.properties.get(col_idx) { *v } else { None } }) .collect(); Float64Array::from(buf.as_slice()).into() } } Some(PropValue::Str(_)) | None => { let arr = Array::new_with_length(feature_count_u32(n)); for (i, f) in tile.features.iter().enumerate() { if let Some(PropValue::Str(Some(s))) = f.properties.get(col_idx) { arr.set(idx_u32(i), JsValue::from_str(s)); } } arr.into() } } } /// Convert a single [`PropValue`] to a JS primitive for the per-feature /// compatibility API. Returns `None` for absent values. #[allow(clippy::cast_precision_loss)] pub(crate) fn prop_value_to_js(val: &PropValue) -> Option { match val { PropValue::Bool(v) => v.map(JsValue::from_bool), PropValue::I8(v) => v.map(|n| JsValue::from_f64(f64::from(n))), PropValue::U8(v) => v.map(|n| JsValue::from_f64(f64::from(n))), PropValue::I32(v) => v.map(|n| JsValue::from_f64(f64::from(n))), PropValue::U32(v) => v.map(|n| JsValue::from_f64(f64::from(n))), PropValue::I64(v) => v.map(|n| JsValue::from_f64(n as f64)), PropValue::U64(v) => v.map(|n| JsValue::from_f64(n as f64)), PropValue::F32(v) => v.map(|n| JsValue::from_f64(f64::from(n))), PropValue::F64(v) => v.map(JsValue::from_f64), PropValue::Str(v) => v.as_deref().map(JsValue::from_str), } } ================================================ FILE: rust/mlt-wasm/src/tile.rs ================================================ use js_sys::{Array, Float64Array, Object, Reflect}; use wasm_bindgen::prelude::*; use crate::geometry::LayerGeometry; use crate::layer::DecodedLayer; use crate::properties::{build_prop_cache, prop_value_to_js}; /// A fully decoded MLT tile. /// /// Construct one via [`crate::decode_tile`], then use the index-based accessors /// to read layer metadata and per-feature data. /// /// All decoding is done eagerly at construction time via [`TileLayer`](mlt_core::TileLayer). #[wasm_bindgen] pub struct MltTile { pub(crate) layers: Vec, } #[wasm_bindgen] impl MltTile { // ----------------------------------------------------------------------- // Layer metadata // ----------------------------------------------------------------------- /// Number of layers in this tile. #[must_use] pub fn layer_count(&self) -> usize { self.layers.len() } /// Name of layer `layer_idx`. #[must_use] pub fn layer_name(&self, layer_idx: usize) -> String { self.layers[layer_idx].tile.name.clone() } /// Extent of layer `layer_idx` in tile coordinates (typically 4096). #[must_use] pub fn layer_extent(&self, layer_idx: usize) -> u32 { self.layers[layer_idx].tile.extent } /// Number of features in layer `layer_idx`. #[must_use] pub fn feature_count(&self, layer_idx: usize) -> usize { self.layers[layer_idx].tile.features.len() } // ----------------------------------------------------------------------- // Bulk typed-array accessors // ----------------------------------------------------------------------- /// MVT geometry types — collapses single and multi into `1`/`2`/`3`. #[must_use] pub fn layer_types(&self, layer_idx: usize) -> js_sys::Uint8Array { self.layers[layer_idx].types_array.clone() } /// Original MLT geometry types — preserves the single vs multi distinction. #[must_use] pub fn layer_mlt_types(&self, layer_idx: usize) -> js_sys::Uint8Array { self.layers[layer_idx].mlt_types_array.clone() } /// All feature IDs for layer `layer_idx` as a `Float64Array`. /// /// One `f64` per feature. Absent IDs are `NaN`. #[must_use] pub fn layer_ids(&self, layer_idx: usize) -> Float64Array { let features = &self.layers[layer_idx].tile.features; let floats: Vec = features .iter() .map(|f| { #[expect(clippy::cast_precision_loss)] f.id.map_or(f64::NAN, |v| v as f64) }) .collect(); Float64Array::from(floats.as_slice()) } /// All decoded geometry arrays for layer `layer_idx`, in one call. #[must_use] pub fn layer_geometry(&self, layer_idx: usize) -> LayerGeometry { LayerGeometry::from_values(&self.layers[layer_idx].geometry) } // ----------------------------------------------------------------------- // Bulk property API // ----------------------------------------------------------------------- /// Column names for layer `layer_idx` as a JS `Array` of strings. pub fn layer_property_keys(&self, layer_idx: usize) -> Array { let tile = &self.layers[layer_idx].tile; build_prop_cache(tile).keys } /// All property values for layer `layer_idx` as a JS `Array` of columns. pub fn layer_properties(&self, layer_idx: usize) -> Array { let tile = &self.layers[layer_idx].tile; build_prop_cache(tile).columns } // ----------------------------------------------------------------------- // Compatibility: per-feature API // ----------------------------------------------------------------------- /// Properties for a single feature as a plain JS object. #[must_use] pub fn feature_properties(&self, layer_idx: usize, feature_idx: usize) -> Object { let tile = &self.layers[layer_idx].tile; let obj = Object::new(); if let Some(feature) = tile.features.get(feature_idx) { for (name, val) in tile.property_names.iter().zip(feature.properties.iter()) { if let Some(js_val) = prop_value_to_js(val) { let _ = Reflect::set(&obj, &JsValue::from_str(name), &js_val); } } } obj } } ================================================ FILE: rust/mlt-wasm/tsconfig.json ================================================ { "compilerOptions": { "target": "ES2022", "module": "ES2022", "moduleResolution": "bundler", "lib": ["ES2022", "DOM"], "rootDir": "js", "outDir": "dist", "declaration": true, "declarationMap": true, "sourceMap": true, "strict": true, "skipLibCheck": true }, "include": ["js/**/*"], "exclude": ["node_modules", "dist", "pkg", "**/*.spec.ts", "**/*.bench.ts"] } ================================================ FILE: rust/mlt-wasm/vitest.config.ts ================================================ import topLevelAwait from "vite-plugin-top-level-await"; import wasm from "vite-plugin-wasm"; import { defineConfig } from "vitest/config"; export default defineConfig({ plugins: [wasm(), topLevelAwait()], }); ================================================ FILE: rust/mod.just ================================================ main_crate := 'mlt-core' just := quote(just_executable()) # if running in CI, treat warnings as errors by setting RUSTFLAGS and RUSTDOCFLAGS to '-D warnings' unless they are already set # Use `CI=true just ci-test` to run the same tests as in GitHub CI. # Use `just ci-env-info` to see the current values of RUSTFLAGS and RUSTDOCFLAGS ci_mode := if env('CI', '') != '' {'1'} else {''} export RUSTFLAGS := env('RUSTFLAGS', if ci_mode == '1' {'-D warnings'} else {''}) export RUSTDOCFLAGS := env('RUSTDOCFLAGS', if ci_mode == '1' {'-D warnings'} else {''}) export RUST_BACKTRACE := env('RUST_BACKTRACE', if ci_mode == '1' {'1'} else {'0'}) # Features excluded from cargo-hack --each-feature iterations (heavy optional instrumentation) hack_skip := '--exclude-features __hotpath,__hotpath-alloc,__hotpath-mcp' _default: (just '--list' 'rust') [private] just *args: {{just}} {{args}} # Run integration tests and save its output as the new expected output bless *args: (just 'cargo-install' 'cargo-insta') (just 'cargo-install' 'cargo-hack') cargo hack insta test --accept --unreferenced=delete --each-feature {{hack_skip}} \ --package mlt-core --exclude-no-default-features --exclude-all-features \ {{args}} # Run benchmarks bench *args: (just 'cargo-install' 'cargo-hack') cargo hack bench --all-targets --each-feature {{hack_skip}} \ --package mlt-core --exclude-no-default-features --exclude-all-features \ {{args}} # Run a specific benchmark target bench-one target='encoding_from_mvt': (just 'cargo-install' 'cargo-hack') cargo bench --bench {{target}} -p mlt-core --features __private alias b := build # Build the project build: (just 'cargo-install' 'cargo-hack') cargo hack build --all-targets --each-feature {{hack_skip}} \ --workspace --exclude-no-default-features --exclude-all-features alias c := check # Quick compile without building a binary check: (just 'cargo-install' 'cargo-hack') cargo hack check --all-targets --each-feature {{hack_skip}} \ --workspace --exclude-no-default-features --exclude-all-features cd mlt-core/fuzz && cargo check --all-features # Generate code coverage report to upload to codecov.io ci-coverage: ci-env-info _coverage {{just}} assert-git-is-clean # call it explicitly to avoid just optimizing it to just one call mkdir -p target/llvm-cov # the output-path here is used in the CI workflow cargo llvm-cov report --include-build-script --lcov --output-path target/llvm-cov/lcov.info # Run minimal subset of tests to ensure compatibility with MSRV ci-check: ci-env-info check # Print environment info for CI ci-env-info: #!/usr/bin/env bash set -euo pipefail echo "::group::General environment Information" echo "Running for '{{main_crate}}' crate {{if ci_mode == '1' {'in CI mode'} else {'in dev mode'} }} on {{os()}} / {{arch()}}" echo "PWD {{justfile_directory()}}" {{just}} --version rustc --version cargo --version rustup --version echo "RUSTFLAGS='$RUSTFLAGS'" echo "RUSTDOCFLAGS='$RUSTDOCFLAGS'" echo "RUST_BACKTRACE='$RUST_BACKTRACE'" echo "::endgroup::" # Linux if command -v lscpu &>/dev/null; then echo "::group::lscpu" lscpu echo "::endgroup::" fi if [ -f /proc/cpuinfo ]; then echo "::group::/proc/cpuinfo" cat /proc/cpuinfo echo "::endgroup::" fi # MacOS if command -v sysctl &>/dev/null && command -v egrep &>/dev/null; then echo "::group::sysctl" sysctl -a 2>/dev/null | egrep "^hw" || true # ignore permission denied to some sysctl entries echo "::endgroup::" fi # Run linting as expected by CI ci-lint: ci-env-info test-fmt clippy ci-docs-rs {{just}} assert-git-is-clean # call it explicitly to avoid just optimizing it to just one call # Run all tests as expected by CI ci-test: ci-env-info (just 'java::gen-snippets') test {{just}} assert-git-is-clean # call it explicitly to avoid just optimizing it to just one call # Run all tests as expected by CI for wasm ci-test-wasm: ci-env-info wasm-test {{just}} assert-git-is-clean # call it explicitly to avoid just optimizing it to just one call # Run minimal subset of tests to ensure compatibility with MSRV ci-test-msrv: ci-env-info #!/usr/bin/env bash set -euo pipefail # For MSRV, relax all warnings to avoid different warnings per rust version unset CI RUSTFLAGS RUSTDOCFLAGS RUST_BACKTRACE # Pre-install cargo-hack to avoid CI binstall issues {{just}} cargo-install cargo-hack {{just}} rust::ci-check {{just}} java::gen-snippets {{just}} rust::test {{just}} assert-git-is-clean # Build the layer target (installs `cargo fuzz`) ci-fuzz-build target: (cargo-fuzz 'build' target '--target' 'x86_64-unknown-linux-gnu') # Fuzz the target for 90 seconds (installs `cargo fuzz`) ci-fuzz-run target: (cargo-fuzz 'run' target '--target' 'x86_64-unknown-linux-gnu' '--' '-max_total_time=90' '-max_len=300' '-rss_limit_mb=2048') # Run coverage against a previously generated corpus and export LCOV for Codecov upload. # Output: target/llvm-cov/fuzz-.lcov [working-directory: 'mlt-core'] ci-fuzz-cov target: (just 'cargo-install' 'cargo-fuzz') #!/usr/bin/env bash set -euo pipefail LCOV_OUT="../target/llvm-cov/fuzz-{{target}}.lcov" mkdir -p "$(dirname "$LCOV_OUT")" {{just}} rust::fuzz-llvm-cov {{target}} "$LCOV_OUT" export --format=lcov echo "Fuzz coverage LCOV: $LCOV_OUT" # Build and package release binary for CI ci-build-release target artifact_name: #!/usr/bin/env bash set -euo pipefail rustup target add {{target}} cargo build --release --target {{target}} --package mlt --bin mlt cd target/{{target}}/release if [ -f mlt.exe ]; then 7z a ../../../../{{artifact_name}} mlt.exe else tar -czf ../../../../{{artifact_name}} mlt fi # Output version and tag for GITHUB_OUTPUT (append with >> $GITHUB_OUTPUT) ci-get-release-info package='mlt': #!/usr/bin/env bash set -euo pipefail VERSION=$({{just}} rust::get-crate-field version {{package}}) echo "version=$VERSION" echo "tag=rust-{{package}}-v$VERSION" # Clean all build artifacts clean: cargo clean rm -f Cargo.lock # Run cargo clippy to lint the code clippy *args: (just 'cargo-install' 'cargo-hack') cargo clippy --all-targets --all-features --workspace {{args}} cd mlt-core/fuzz && cargo clippy --all-features {{args}} # Generate code coverage report and open it coverage: _coverage cargo llvm-cov report --include-build-script --open # Generate code coverage report. Installs `cargo llvm-cov` _coverage: (just 'cargo-install' 'cargo-hack') (just 'cargo-install' 'cargo-llvm-cov') (just 'java::gen-snippets') cargo llvm-cov clean --workspace cargo hack llvm-cov --no-report --all-targets --each-feature {{hack_skip}} \ --workspace --exclude-no-default-features --exclude-all-features rm -rf ../test/synthetic/0x01-rust cargo llvm-cov run --no-report -p mlt-synthetics cargo llvm-cov run --no-report -p mlt -- ls --validate-to-json --details basic ../test/synthetic/ rustup toolchain install nightly --profile minimal --component llvm-tools cargo +nightly llvm-cov --no-report --doctests --workspace # Run the encoder with hotpath profiling (timing + allocation tracking) profile *args: cargo run --release -p mlt --features='__hotpath,__hotpath-alloc,__hotpath-mcp' -- {{args}} # Download ~5k benchmark tiles (z0-z6) from Protomaps into outdir. # Skips if a previous complete download is detected (.complete sentinel). # ODbL-licensed OSM data; extraction explicitly permitted by Protomaps. download-benchmark-tiles outdir='/tmp/benchmark-tiles' maxzoom='6': (just 'install-pmtiles') #!/usr/bin/env bash set -euo pipefail if [ -f "{{outdir}}/.complete" ]; then echo "{{outdir}} already has tiles, skipping download" exit 0 fi TMPFILE=$(mktemp /tmp/benchmark-XXXXXX.pmtiles) trap 'rm -f "$TMPFILE"' EXIT pmtiles extract \ https://data.source.coop/protomaps/openstreetmap/v4.pmtiles \ "$TMPFILE" \ --maxzoom={{maxzoom}} python3 - "{{outdir}}" "{{maxzoom}}" "$TMPFILE" << 'PYEOF' import gzip, os, sys from pmtiles.reader import Reader, MmapSource, Compression out, maxzoom, src = sys.argv[1], int(sys.argv[2]), sys.argv[3] os.makedirs(out, exist_ok=True) count = 0 with open(src, "rb") as f: reader = Reader(MmapSource(f)) is_gzip = reader.header()["tile_compression"] == Compression.GZIP for z in range(maxzoom + 1): for x in range(2 ** z): for y in range(2 ** z): data = reader.get(z, x, y) if data: if is_gzip: data = gzip.decompress(data) with open(os.path.join(out, f"{z}_{x}_{y}.mvt"), "wb") as of: of.write(data) count += 1 print(f"Extracted {count} tiles to {out}") PYEOF touch "{{outdir}}/.complete" # Build and open code documentation docs *args='--open': DOCS_RS=1 cargo doc --no-deps {{args}} --features arbitrary --workspace alias f := fmt fmt: (_fmt) (_fmt '--manifest-path' 'mlt-core/fuzz/Cargo.toml') # Reformat code. If nightly is available, use it for better results. _fmt *ARGS: #!/usr/bin/env bash set -euo pipefail if (rustup toolchain list | grep nightly && rustup component list --toolchain nightly | grep rustfmt) &> /dev/null; then echo 'Reformatting Rust code using nightly Rust fmt to sort imports' cargo +nightly fmt --all {{ARGS}} -- --config imports_granularity=Module,group_imports=StdExternalCrate else echo 'Reformatting Rust with the stable cargo fmt. Install nightly with `rustup install nightly` for better results' cargo fmt --all {{ARGS}} fi # Reformat all Cargo.toml files (installs `cargo sort`) fmt-toml *args: (just 'cargo-install' 'cargo-sort') cargo sort --workspace --grouped {{args}} # Fuzz the layer target (installs `cargo fuzz`) fuzz target='layer' *args: (cargo-fuzz 'run' target '--target' 'x86_64-unknown-linux-gnu' '--' args) # Seed the layer fuzz corpus from all .mlt test files (run once, or after new .mlts are added) fuzz-seed target='layer': (just 'cargo-install' 'fd' 'fd-find') mkdir -p mlt-core/fuzz/corpus/{{target}} fd . ../test --extension mlt --threads=1 --exec cp --force {} mlt-core/fuzz/corpus/{{target}}/ {{just}} rust::fuzz-cmin {{target}} # Minimize the fuzz corpus for target (slow — run occasionally to prune redundant entries) fuzz-cmin target='layer': (cargo-fuzz 'cmin' target '--target' 'x86_64-unknown-linux-gnu') # Collect coverage from the fuzz corpus and open an HTML report (installs `cargo fuzz`, `rustfilt`) [working-directory: 'mlt-core'] fuzz-cov target='layer': (just 'cargo-install' 'cargo-fuzz') (just 'cargo-install' 'rustfilt') #!/usr/bin/env bash set -euo pipefail HTML_DIR="fuzz/coverage/{{target}}/html" mkdir -p "$HTML_DIR" {{just}} rust::fuzz-llvm-cov {{target}} "" show \ --format=html \ --output-dir="$HTML_DIR" \ --show-instantiations \ --show-line-counts-or-regions \ --Xdemangler=rustfilt echo "Coverage report: $HTML_DIR/index.html" xdg-open "$HTML_DIR/index.html" 2>/dev/null || open "$HTML_DIR/index.html" 2>/dev/null || true # Minimize the crash in file (installs `cargo fuzz`) fuzz-tmin file target='layer': (cargo-fuzz 'tmin' target file) # Get any package's field from the metadata get-crate-field field package=main_crate: (just 'assert-cmd' 'jq') cargo metadata --format-version 1 | jq -e -r '.packages | map(select(.name == "{{package}}")) | first | .{{field}} | select(. != null)' # Get the minimum supported Rust version (MSRV) for the crate get-msrv package=main_crate: (get-crate-field 'rust_version' package) # Run formatting, cargo check, and clippy for local development lint: fmt check clippy test # Find the minimum supported Rust version and update Cargo.toml msrv: (just 'cargo-install' 'cargo-msrv') cargo msrv find --write-msrv --ignore-lockfile --component rustfmt -- {{just}} rust::ci-test-msrv # Build mlt-py and launch a Python interpreter with mlt pre-imported [working-directory: 'mlt-py'] py: (just 'cargo-install' 'maturin') #!/usr/bin/env bash set -euo pipefail if [[ ! -d ".venv" ]]; then echo "Please make sure to create your virtual environment by running 'python -m venv .venv'" exit 1 fi source .venv/bin/activate maturin develop python3 -i -c "import maplibre_tiles; print('maplibre_tiles ready — try: maplibre_tiles.decode_mlt(data)')" py-publish: docker run --rm -v $(pwd):/io ghcr.io/pyo3/maturin build --release # Run cargo-release release *args='': (just 'cargo-install' 'release-plz') release-plz {{args}} # Check semver compatibility with prior published version semver *args: (just 'cargo-install' 'cargo-semver-checks') (just 'cargo-install' 'cargo-hack') cargo hack semver-checks --each-feature {{hack_skip}} \ --package mlt-core --exclude-no-default-features --exclude-all-features \ {{args}} # Regenerate the Python type stub for mlt-py sync-pyo3-stubs: cargo run -p mlt-py --bin stub_gen --features abi3 # Sync the version field in mlt-py/pyproject.toml to match mlt-py/Cargo.toml sync-pyo3-version: #!/usr/bin/env bash set -euo pipefail VERSION=$({{just}} rust::get-crate-field version mlt-py) PYPROJECT="mlt-py/pyproject.toml" sed -i "s/^version = \".*\"/version = \"$VERSION\"/" "$PYPROJECT" echo "Synced $PYPROJECT to version $VERSION" # Sync the version field in mlt-wasm/package.json to match mlt-wasm/Cargo.toml sync-wasm-version: #!/usr/bin/env bash set -euo pipefail VERSION=$({{just}} rust::get-crate-field version mlt-wasm) PACKAGE_JSON="mlt-wasm/package.json" jq --arg VERSION "$VERSION" '.version = $VERSION' "$PACKAGE_JSON" > "$PACKAGE_JSON.tmp" && mv "$PACKAGE_JSON.tmp" "$PACKAGE_JSON" echo "Synced $PACKAGE_JSON to version $VERSION" if [ -f "mlt-wasm/package-lock.json" ]; then PACKAGE_LOCK="mlt-wasm/package-lock.json" jq --arg VERSION "$VERSION" '.version = $VERSION | .packages[""].version = $VERSION' "$PACKAGE_LOCK" > "$PACKAGE_LOCK.tmp" && mv "$PACKAGE_LOCK.tmp" "$PACKAGE_LOCK" echo "Synced $PACKAGE_LOCK to version $VERSION" fi alias t := test # Run all tests test: (just 'cargo-install' 'cargo-hack') cargo hack test --all-targets --each-feature {{hack_skip}} \ --workspace --exclude-no-default-features --exclude-all-features {{just}} rust::generate-synthetic-mlts {{just}} rust::test-doc # Test documentation generation test-doc: (docs '--document-private-items') cargo test --doc --features arbitrary --package mlt-core # Simulate docs.rs for all workspace crates and verify docs can build offline. ci-docs-rs: #!/usr/bin/env bash set -euo pipefail rustup toolchain install nightly --profile minimal export DOCS_RS=1 export RUSTDOCFLAGS="${RUSTDOCFLAGS:+$RUSTDOCFLAGS } --cfg docsrs" cargo fetch --locked CARGO_NET_OFFLINE=true cargo +nightly doc --workspace --no-deps --locked # Test code formatting test-fmt: (fmt-toml '--check' '--check-format') cargo fmt --all -- --check # Test workspace publishing with --dry-run test-publish: cargo publish --workspace --dry-run # Find unused dependencies udeps: (just 'cargo-install' 'cargo-udeps') cargo +nightly udeps --all-targets --workspace --all-features # Update all dependencies, including breaking changes update: cargo +nightly -Z unstable-options update --breaking cargo update cd mlt-core/fuzz && cargo +nightly -Z unstable-options update --breaking cd mlt-core/fuzz && cargo update # Generate synthetic .mlt files and ensure there are no duplicates generate-synthetic-mlts *args: #!/usr/bin/env bash set -euo pipefail rm -rf ../test/synthetic/0x01-rust cargo run -p mlt-synthetics -- {{args}} {{just}} rust::validate-synthetic {{just}} _assert-all-mlt-files-different # Create .dump files for all .mlt files in test/synthetic for all languages generate-synthetic-dumps: (just 'cargo-install' 'fd' 'fd-find') cargo build --bin mlt fd . ../test/synthetic -e mlt -x sh -c 'target/debug/mlt dump {} > {.}.dump || echo "Failed to dump {}"' # Run mlt-wasm benches [working-directory: 'mlt-wasm'] wasm-bench: wasm-build npm run bench # Build the mlt-wasm package (compiles Rust to WASM and TypeScript) [working-directory: 'mlt-wasm'] wasm-build: (just 'cargo-install' 'wasm-pack') npm ci --no-fund --no-audit npm run build # Run mlt-wasm tests [working-directory: 'mlt-wasm'] wasm-test: wasm-build npm run test # Validate every .mlt in test/synthetic against corresponding .json (GeoJSON) validate-synthetic: {{just}} mlt ls --validate-to-json --details basic ../test/synthetic/ [private] [working-directory: 'mlt-core'] cargo-fuzz *args: (just 'cargo-install' 'cargo-fuzz') cargo +nightly fuzz {{args}} # Resolve llvm-cov from the nightly sysroot and run it against the fuzz profdata for target. # Pass output_file="" to write to stdout, or a path to redirect stdout there. [private] [working-directory: 'mlt-core'] fuzz-llvm-cov target output_file *args: #!/usr/bin/env bash set -euo pipefail rustup component add llvm-tools-preview --toolchain nightly cargo +nightly fuzz coverage {{target}} --target x86_64-unknown-linux-gnu SYSROOT=$(rustup run nightly rustc --print sysroot) HOST=$(rustup run nightly rustc -vV | sed -n 's/^host: //p') LLVM_COV="$SYSROOT/lib/rustlib/$HOST/bin/llvm-cov" FUZZ_DIR="fuzz" TARGET_BIN=$(find target -type f -name '{{target}}' -path '*/coverage/*/release/{{target}}' 2>/dev/null | head -1) echo "target_bin: $TARGET_BIN" >&2 PROFDATA="$FUZZ_DIR/coverage/{{target}}/coverage.profdata" [ -n "{{output_file}}" ] && exec > "{{output_file}}" "$LLVM_COV" {{args}} "$TARGET_BIN" \ --instr-profile="$PROFDATA" \ --ignore-filename-regex="(/.cargo/registry|/rustup/toolchains)" ================================================ FILE: rust/tomlfmt.toml ================================================ table_order = [ 'workspace', 'package', 'lib', 'bin', 'example', 'bench', 'features', 'dependencies', 'build-dependencies', 'dev-dependencies', 'profile', 'lints', ] ================================================ FILE: spec/schema/mlt_tileset_metadata.proto ================================================ syntax = "proto3"; package mlt; option java_package = "org.maplibre.mlt.metadata.tileset"; message TileSetMetadata { int32 version = 1; repeated FeatureTableSchema featureTables = 2; optional string name = 3; optional string description = 4; optional string attribution = 5; optional int32 minZoom = 6; optional int32 maxZoom = 7; // order left, bottom, right, top in WGS84 repeated double bounds = 8; // order longitude, latitude in WGS84 repeated double center = 9; } message FeatureTableSchema { string name = 1; repeated Column columns = 2; } // Column are top-level types in the schema message Column { string name = 1; // specifies if the values are optional in the column and a present stream should be used bool nullable = 2; ColumnScope columnScope = 3; oneof type { ScalarColumn scalarType = 4; ComplexColumn complexType = 5; } } message ScalarColumn { // this belongs elsewhere, but is here for now bool longID = 1; oneof type { ScalarType physicalType = 4; LogicalScalarType logicalType = 5; } } // The type tree is flattened in to a list via a pre-order traversal // Represents a column if it is a root (top-level) type or a child of a nested type message ComplexColumn { oneof type { ComplexType physicalType = 4; LogicalComplexType logicalType = 5; } // The complex type Geometry and the logical type BINARY have no children since there layout is implicit known. // RangeMap has only one child specifying the type of the value since the key is always a vec2. repeated Field children = 6; } // Fields define nested or leaf types in the schema as part of a complex type definition message Field { // name and nullable are only needed in combination with a struct not for vec, list and map // Map -> has the order key type, value type optional string name = 1; optional bool nullable = 2; oneof type { ScalarField scalarField = 3; ComplexField complexField = 4; } } message ScalarField { oneof type { ScalarType physicalType = 1; LogicalScalarType logicalType = 2; } } message ComplexField { oneof type { ComplexType physicalType = 1; LogicalComplexType logicalType = 2; } repeated Field children = 3; } enum ColumnScope { // 1:1 Mapping of property and feature -> id and geometry FEATURE = 0; // For M-Values -> 1:1 Mapping for property and vertex VERTEX = 1; } enum ScalarType { BOOLEAN = 0; INT_8 = 1; UINT_8 = 2; INT_32 = 3; UINT_32 = 4; INT_64 = 5; UINT_64 = 6; FLOAT = 7; DOUBLE = 8; STRING = 9; } enum ComplexType { GEOMETRY = 0; STRUCT = 1; } enum LogicalScalarType { ID = 0; } enum LogicalComplexType { // physical type: list BINARY = 0; // physical type: map> -> special data structure which can be used for a efficient representation of linear referencing RANGE_MAP = 1; } ================================================ FILE: test/.gitignore ================================================ output/ # Ignore generated stream files. expected/*/*/*.bin expected/*/*/*.json # The per-tile directories also need to be hidden expected/*/*/*/* # Dump files **/*.dump ================================================ FILE: test/.java-version ================================================ 24 ================================================ FILE: test/convert_tiles ================================================ #!/bin/bash # terminate when subshell is killed trap 'trap " " SIGINT SIGTERM SIGHUP; kill 0; exit 1' SIGINT SIGTERM SIGHUP # Build a metadata-and-tile file by concatenating the metadata size, metadata, and tile data bundle_meta () { TILE_FILE="$1" META_FILE="$2" OUT_FILE="$3" printf "%.8x\n" $(stat --format=%s "$META_FILE") | \ sed -E 's/(..)(..)(..)(..)/0: \4\3\2\1/' | \ xxd -r -l 4 -g 4 | \ cat - "$META_FILE" "$TILE_FILE" >"$OUT_FILE" } convert_tile () { z=$1 x=$2 y=$3 SOURCE_PATH="$4" TARGET_PATH="$5" MVT_FILE=${z}_${x}_${y}.mvt TILE_PATH="$TARGET_PATH/$z/$x" mkdir -p "$TILE_PATH" if [ ! -f "$TARGET_PATH/$z/$x/$y.mlt" ] || [ ! -f "$TARGET_PATH/$z/$x/$y.mlt.meta.pbf" ]; then rm -f "$TILE_PATH/$y.mltm" # Write MLT and metadata to target location (cd ../java ; $ENCODE -mvt "$SOURCE_PATH/$MVT_FILE" -mlt "$TARGET_PATH/$z/$x/$y.mlt" -metadata $6 $7 $8) RESULT=$? if [ $RESULT -ne 0 ] ; then echo " $SOURCE_PATH/$MVT_FILE Failed: $RESULT" rm -f "$TILE_PATH/$y.mlt"* return $RESULT fi fi if [ ! -f "$TILE_PATH/$y.mltm" ]; then # Combine MLT and metadata bundle_meta "$TILE_PATH/$y.mlt" "$TILE_PATH/$y.mlt.meta.pbf" "$TILE_PATH/$y.mltm" fi return 0 } if [ -f ignore_list_basic ]; then IFS=$'\r\n' command eval 'IGNORE_LIST_BASIC=($(cat ignore_list_basic))' fi if [ -f ignore_list_advanced ]; then IFS=$'\r\n' command eval 'IGNORE_LIST_ADVANCED=($(cat ignore_list_advanced))' fi isIgnoreBasic () { [[ " ${IGNORE_LIST_BASIC[@]} " =~ " $1 " ]] && return 0 || return 1 } isIgnoreAdvanced () { [[ " ${IGNORE_LIST_ADVANCED[@]} " =~ " $1 " ]] && return 0 || return 1 } # Find OMT tile files FILES=$(cd fixtures/omt ; ls -1 *.mvt) ENCODE="java -jar build/libs/encode.jar" # Relative to root SRC_PATH=test/fixtures/omt BASIC_PATH=test/expected/omt/basic ADVANCED_PATH=test/expected/omt/advanced MVT_PATH=test/expected/omt/mvt echo -r \\nProcessing tiles ... for file in $FILES ; do read z x y <<< $(echo $file | sed -E 's/([0-9]+)_([0-9]+)_([0-9]+)\.mvt/\1 \2 \3/') echo " $z:$x,$y ..." MVT_FILE=${z}_${x}_${y}.mvt mkdir -p "../$BASIC_PATH/$z/$x" "../$ADVANCED_PATH/$z/$x" "../$MVT_PATH/$z/$x" if ! isIgnoreBasic "$z/$x/$y"; then convert_tile $z $x $y "../$SRC_PATH" "../$BASIC_PATH" if [ $? -ne 0 ]; then echo $z/$x/$y >> ignore_list_basic fi fi if ! isIgnoreAdvanced "$z/$x/$y"; then convert_tile $z $x $y "../$SRC_PATH" "../$ADVANCED_PATH" -advanced if [ $? -ne 0 ]; then echo $z/$x/$y >> ignore_list_advanced fi fi cp "../$SRC_PATH/$file" "../$MVT_PATH/$z/$x/$y.pbf" done echo -e \\nBuilding metadata ... cat >"../$BASIC_PATH/metadata.json" << EOF { "name": "omt-basic", "type": "baselayer", "format": "mlt", "description": "description", "version": "1.0", "formatter": null, "bounds": "-179.9999999749438,-69.99999999526695,179.9999999749438,84.99999999782301" } EOF cat >"../$ADVANCED_PATH/metadata.json" << EOF { "name": "omt-advanced", "type": "baselayer", "format": "mlt", "description": "description", "version": "1.0", "formatter": null, "bounds": "-179.9999999749438,-69.99999999526695,179.9999999749438,84.99999999782301" } EOF cat >"../$MVT_PATH/metadata.json" << EOF { "name": "omt-ref", "type": "baselayer", "format": "pbf", "description": "description", "version": "1.0", "formatter": null, "bounds": "-179.9999999749438,-69.99999999526695,179.9999999749438,84.99999999782301" } EOF rm -f omt-basic-mlt.mbtiles omt-advanced-mlt.mbtiles omt-ref.mbtiles echo -e \\nBuilding omt-basic-mlt.mbtiles ... mb-util --image_format=mltm --scheme=tms "../$BASIC_PATH" omt-basic-mlt.mbtiles echo -e \\nBuilding omt-advanced-mlt.mbtiles ... mb-util --image_format=mltm --scheme=tms "../$ADVANCED_PATH" omt-advanced-mlt.mbtiles echo -e \\nBuilding omt-ref.mbtiles ... mb-util --image_format=pbf --scheme=tms "../$MVT_PATH" omt-ref.mbtiles ================================================ FILE: test/expected/tag0x01/simple/LICENSE ================================================ These test fixtures have the same license as the rest of the documentation and content of this repo: CC0 1.0 Universal They were hand crafted by Dane Springmeyer, June 15, 2024 ================================================ FILE: test/expected/tag0x01/simple/line-boolean.mlt.geojson ================================================ { "layers": [ { "name": "layer", "version": 1, "extent": 4096, "features": [ { "type": "Feature", "id": 1, "geometry": { "type": "LineString", "coordinates": [ [ -171.56112670898438, 83.6767918001807 ], [ -171.56112670898438, 83.67618677120873 ], [ -171.55563354492188, 83.67618677120873 ] ] }, "properties": { "key": true } } ] } ] } ================================================ FILE: test/expected/tag0x01/simple/multiline-boolean.mlt.geojson ================================================ { "layers": [ { "name": "layer", "version": 1, "extent": 4096, "features": [ { "type": "Feature", "id": 1, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ -171.56112670898438, 83.6767918001807 ], [ -171.56112670898438, 83.67618677120873 ], [ -171.55563354492188, 83.67618677120873 ] ], [ [ -171.5618133544922, 83.67686742474851 ], [ -171.56044006347656, 83.67656492107244 ] ] ] }, "properties": { "key": true } } ] } ] } ================================================ FILE: test/expected/tag0x01/simple/multipoint-boolean.mlt.geojson ================================================ { "layers": [ { "name": "layer", "version": 1, "extent": 4096, "features": [ { "type": "Feature", "id": 1, "geometry": { "type": "MultiPoint", "coordinates": [ [ -171.55906677246094, 83.67641366382949 ], [ -171.56044006347656, 83.6767918001807 ] ] }, "properties": { "key": true } } ] } ] } ================================================ FILE: test/expected/tag0x01/simple/multipolygon-boolean.mlt.geojson ================================================ { "layers": [ { "name": "layer", "version": 1, "extent": 4096, "features": [ { "type": "Feature", "id": 1, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -171.5625, 83.67694304841552 ], [ -171.55563354492188, 83.67694304841552 ], [ -171.55563354492188, 83.67618677120873 ], [ -171.5625, 83.67618677120873 ], [ -171.5625, 83.67694304841552 ] ] ], [ [ [ -171.55494689941406, 83.6761111385334 ], [ -171.54876708984375, 83.6761111385334 ], [ -171.54876708984375, 83.67543040391408 ], [ -171.55494689941406, 83.67543040391408 ], [ -171.55494689941406, 83.6761111385334 ] ], [ [ -171.55357360839844, 83.67595987048006 ], [ -171.55357360839844, 83.67565732356232 ], [ -171.5508270263672, 83.67565732356232 ], [ -171.5508270263672, 83.67595987048006 ], [ -171.55357360839844, 83.67595987048006 ] ] ] ] }, "properties": { "key": true } } ] } ] } ================================================ FILE: test/expected/tag0x01/simple/point-boolean.mlt.geojson ================================================ { "layers": [ { "name": "layer", "version": 1, "extent": 4096, "features": [ { "type": "Feature", "id": 1, "geometry": { "type": "Point", "coordinates": [ -171.5453338623047, 83.67565732356232 ] }, "properties": { "key": true } } ] } ] } ================================================ FILE: test/expected/tag0x01/simple/polygon-boolean.mlt.geojson ================================================ { "layers": [ { "name": "layer", "version": 1, "extent": 4096, "features": [ { "type": "Feature", "id": 1, "geometry": { "type": "Polygon", "coordinates": [ [ [ -171.56044006347656, 83.67648929290141 ], [ -171.5570068359375, 83.67603550495716 ], [ -171.54876708984375, 83.6743713383338 ], [ -171.56044006347656, 83.67648929290141 ] ] ] }, "properties": { "key": true } } ] } ] } ================================================ FILE: test/fixtures/fastpfor/README.md ================================================ # FastPFOR fixtures This directory contains cross-language **FastPFOR** test vectors extracted from the C++ tests. ## Origin The files correspond to the `compressed{1..4}` / `uncompressed{1..4}` arrays in: - `cpp/test/test_fastpfor.cpp` ## File formats For each `vectorN`: - `vectorN_encoded.bin` - Big-endian bytes of the FastPFOR-encoded **32-bit word stream** (`uint32_t[]` in C++). - This is the FastPFOR *wire format* as consumed by TS `decodeFastPfor` (big-endian int32 words). - Fixtures are stored in canonical form: trailing `0x00000000` padding words are trimmed. - `vectorN_decoded.bin` - Big-endian bytes of the expected **decoded int32 values** (`uint32_t[]` in C++). - When interpreted as signed int32, values use two’s complement (e.g. the `-100..99` range in `vector3`). ================================================ FILE: test/fixtures/omt-planet-20260112.mvt.max1.pmtiles.txt ================================================ Downloaded from https://tile.openstreetmap.jp/static/planet-20260126.pmtiles Subset with `pmtiles extract planet-20260112.pmtiles omt-planet-20260112.mvt.max1.pmtiles --maxzoom=1` ================================================ FILE: test/fixtures/simple/LICENSE ================================================ These test fixtures have the same license as the rest of the documentation and content of this repo: CC0 1.0 Universal They were hand crafted by Dane Springmeyer, June 15, 2024 ================================================ FILE: test/omt-advanced-mlt.mbtiles ================================================ [File too large to display: 14.8 MB] ================================================ FILE: test/omt-basic-mlt.mbtiles ================================================ [File too large to display: 18.0 MB] ================================================ FILE: test/omt-ref.mbtiles ================================================ [File too large to display: 32.7 MB] ================================================ FILE: test/synthetic/0x01/extent_1073741824.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 1073741824, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 0, 0 ], [ 1073741823, 1073741823 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/extent_131072.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 131072, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 0, 0 ], [ 131071, 131071 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/extent_4096.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 4096, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 0, 0 ], [ 4095, 4095 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/extent_512.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 512, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 0, 0 ], [ 511, 511 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/extent_buf_1073741824.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 1073741824, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ -42, -42 ], [ 1073741866, 1073741866 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/extent_buf_1073741824.mlt ================================================ *layer102B SS ================================================ FILE: test/synthetic/0x01/extent_buf_131072.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 131072, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ -42, -42 ], [ 131114, 131114 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/extent_buf_131072.mlt ================================================ $layer102BSS ================================================ FILE: test/synthetic/0x01/extent_buf_4096.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 4096, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ -42, -42 ], [ 4138, 4138 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/extent_buf_4096.mlt ================================================ !layer1 02BSSAA ================================================ FILE: test/synthetic/0x01/extent_buf_512.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 512, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ -42, -42 ], [ 554, 554 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/extent_buf_512.mlt ================================================ !layer102BSS ================================================ FILE: test/synthetic/0x01/fpf_align_1.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "a", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/fpf_align_2.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/fpf_align_3.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/fpf_align_4.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/fpf_align_5.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/fpf_align_6.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/fpf_align_7.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/fpf_align_8.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "aaaaaaaa", "v": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/id.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 100, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/id64.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 9234567890, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/id_min.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 0, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/ids.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/ids64.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 9234567890, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 9234567890, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 9234567890, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 9234567890, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/ids64_delta.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 9234567890, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 9234567890, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 9234567890, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 9234567890, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/ids64_delta_rle.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 9234567890, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 9234567890, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 9234567890, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 9234567890, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/ids64_opt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 9234567890, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 101, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 105, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 106, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/ids64_opt_delta.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 9234567890, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 101, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 105, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 106, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/ids64_rle.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 9234567890, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 9234567890, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 9234567890, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 9234567890, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/ids_delta.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/ids_delta_rle.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/ids_opt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 100, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 101, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 105, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 106, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/ids_opt_delta.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 100, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 101, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 105, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 106, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/ids_rle.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/line.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 11, 52 ], [ 71, 72 ], [ 61, 22 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/line.mlt ================================================ layer1P02Bhx(c ================================================ FILE: test/synthetic/0x01/line_morton_curve_morton.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 0, 0 ], [ 8, 0 ], [ 0, 8 ], [ 8, 8 ], [ 16, 0 ], [ 24, 0 ], [ 16, 8 ], [ 24, 8 ], [ 0, 16 ], [ 8, 16 ], [ 0, 24 ], [ 8, 24 ], [ 16, 16 ], [ 24, 16 ], [ 16, 24 ], [ 24, 24 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/line_morton_curve_no_morton.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 0, 0 ], [ 8, 0 ], [ 0, 8 ], [ 8, 8 ], [ 16, 0 ], [ 24, 0 ], [ 16, 8 ], [ 24, 8 ], [ 0, 16 ], [ 8, 16 ], [ 0, 24 ], [ 8, 24 ], [ 16, 16 ], [ 24, 16 ], [ 16, 24 ], [ 24, 24 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/line_zero_length.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 6, 6 ], [ 6, 6 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_line_line.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_line_line.mlt ================================================ (layer1P02B L2?2 ================================================ FILE: test/synthetic/0x01/mix_2_line_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_line_mline.mlt ================================================ 2layer1P012B L2w$$$  ================================================ FILE: test/synthetic/0x01/mix_2_line_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_line_mpoly.mlt ================================================ Alayer1P0123B L2c + f@ ================================================ FILE: test/synthetic/0x01/mix_2_line_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_line_mpt.mlt ================================================ ,layer1P012B L2Y 8 ================================================ FILE: test/synthetic/0x01/mix_2_line_poly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_line_poly.mlt ================================================ .layer1P023B L2\." ================================================ FILE: test/synthetic/0x01/mix_2_line_polyh.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_line_polyh.mlt ================================================ 4layer1P023B L2VEK(\"7+ ================================================ FILE: test/synthetic/0x01/mix_2_mline_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_mline_mline.mlt ================================================ 8layer1P012B0$$$ g$$$  ================================================ FILE: test/synthetic/0x01/mix_2_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_mline_mpoly.mlt ================================================ Glayer1P0123B0$$$ 7S + f@ ================================================ FILE: test/synthetic/0x01/mix_2_mpoly_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_mpoly_mpoly.mlt ================================================ Slayer1P0123B$%( + f@W + f@ ================================================ FILE: test/synthetic/0x01/mix_2_mpoly_mpoly_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_mpt_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_mpt_mline.mlt ================================================ 2layer1P012B 2 8u$$$  ================================================ FILE: test/synthetic/0x01/mix_2_mpt_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_mpt_mpoly.mlt ================================================ Alayer1P0123B 2 8a + f@ ================================================ FILE: test/synthetic/0x01/mix_2_mpt_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_mpt_mpt.mlt ================================================ (layer1P01B 2 8!W 8 ================================================ FILE: test/synthetic/0x01/mix_2_poly_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_poly_mline.mlt ================================================ 7layer1P0123Bn ." e$$$  ================================================ FILE: test/synthetic/0x01/mix_2_poly_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_poly_mpoly.mlt ================================================ Clayer1P0123Bn ."  + f@ ================================================ FILE: test/synthetic/0x01/mix_2_poly_mpoly_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_poly_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_poly_mpt.mlt ================================================ 2layer1P0123B n ."  8 ================================================ FILE: test/synthetic/0x01/mix_2_poly_poly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_poly_poly.mlt ================================================ .layer1P023B n ." '!." ================================================ FILE: test/synthetic/0x01/mix_2_poly_poly_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_poly_polyh.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_poly_polyh.mlt ================================================ 5layer1P023Bn ." -K(\"7+ ================================================ FILE: test/synthetic/0x01/mix_2_poly_polyh_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_polyh_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_polyh_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_polyh_mpoly.mlt ================================================ Ilayer1P0123BhFK(\"7+ !C + f@ ================================================ FILE: test/synthetic/0x01/mix_2_polyh_mpoly_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_polyh_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_polyh_mpt.mlt ================================================ 8layer1P0123BhFK(\"7+ #9 8 ================================================ FILE: test/synthetic/0x01/mix_2_polyh_polyh.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_polyh_polyh.mlt ================================================ <layer1P023BhFK(\"7+ 8%K(\"7+ ================================================ FILE: test/synthetic/0x01/mix_2_polyh_polyh_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_pt_line.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_pt_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_pt_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_pt_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_pt_poly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_pt_polyh.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_2_pt_pt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_line_mline_line.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_line_mline_line.mlt ================================================ :layer1P012B L2w$$$ ;/2 ================================================ FILE: test/synthetic/0x01/mix_3_line_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_line_mline_mpoly.mlt ================================================ Olayer1P0123B"" L2w$$$ 7S + f@ ================================================ FILE: test/synthetic/0x01/mix_3_line_mpoly_line.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_line_mpoly_line.mlt ================================================ Jlayer1P0123B L2c + f@32 ================================================ FILE: test/synthetic/0x01/mix_3_line_mpt_line.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_line_mpt_line.mlt ================================================ 4layer1P012B L2Y 8#=2 ================================================ FILE: test/synthetic/0x01/mix_3_line_mpt_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_line_mpt_mline.mlt ================================================ :layer1P012B L2Y 8u$$$  ================================================ FILE: test/synthetic/0x01/mix_3_line_mpt_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_line_mpt_mpoly.mlt ================================================ Ilayer1P0123B L2Y 8a + f@ ================================================ FILE: test/synthetic/0x01/mix_3_line_poly_line.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_line_poly_line.mlt ================================================ 7layer1P023B L2\."  2 ================================================ FILE: test/synthetic/0x01/mix_3_line_poly_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_line_poly_mline.mlt ================================================ @layer1P0123B L2\." e$$$  ================================================ FILE: test/synthetic/0x01/mix_3_line_poly_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_line_poly_mpoly.mlt ================================================ Llayer1P0123B L2\."  + f@ ================================================ FILE: test/synthetic/0x01/mix_3_line_poly_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_line_poly_mpt.mlt ================================================ ;layer1P0123B L2\."  8 ================================================ FILE: test/synthetic/0x01/mix_3_line_poly_polyh.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_line_poly_polyh.mlt ================================================ >layer1P023B L2\." -K(\"7+ ================================================ FILE: test/synthetic/0x01/mix_3_line_polyh_line.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_line_polyh_line.mlt ================================================ <layer1P023B L2VEK(\"7+ %2 ================================================ FILE: test/synthetic/0x01/mix_3_line_polyh_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_line_polyh_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_line_polyh_mpoly.mlt ================================================ Qlayer1P0123B$$ L2VEK(\"7+ !C + f@ ================================================ FILE: test/synthetic/0x01/mix_3_line_polyh_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_line_polyh_mpt.mlt ================================================ @layer1P0123B L2VEK(\"7+ #9 8 ================================================ FILE: test/synthetic/0x01/mix_3_line_pt_line.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mline_line_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mline_line_mline.mlt ================================================ @layer1P012B0$$$ ;/2w$$$  ================================================ FILE: test/synthetic/0x01/mix_3_mline_mpoly_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mline_mpoly_mline.mlt ================================================ Ulayer1P0123B&&0$$$ 7S + f@ak$$$  ================================================ FILE: test/synthetic/0x01/mix_3_mline_mpt_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mline_mpt_mline.mlt ================================================ @layer1P012B0$$$ 9I 8u$$$  ================================================ FILE: test/synthetic/0x01/mix_3_mline_poly_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mline_poly_mline.mlt ================================================ Elayer1P0123B0$$$ (q." e$$$  ================================================ FILE: test/synthetic/0x01/mix_3_mline_polyh_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mline_pt_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mpoly_line_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mpoly_line_mpoly.mlt ================================================ [layer1P0123B*+( + f@32c + f@ ================================================ FILE: test/synthetic/0x01/mix_3_mpoly_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mpoly_mline_mpoly.mlt ================================================ `layer1P0123B..( + f@ak$$$ 7S + f@ ================================================ FILE: test/synthetic/0x01/mix_3_mpoly_mpt_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mpoly_mpt_mpoly.mlt ================================================ [layer1P0123B*+( + f@M 8a + f@ ================================================ FILE: test/synthetic/0x01/mix_3_mpoly_poly_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mpoly_poly_mpoly.mlt ================================================ \layer1P0123B*+( + f@#u."  + f@ ================================================ FILE: test/synthetic/0x01/mix_3_mpoly_poly_mpoly_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mpoly_polyh_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mpoly_polyh_mpoly.mlt ================================================ blayer1P0123B00( + f@)9K(\"7+ !C + f@ ================================================ FILE: test/synthetic/0x01/mix_3_mpoly_polyh_mpoly_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mpoly_pt_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mpt_line_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mpt_line_mpt.mlt ================================================ 4layer1P012B 2 8#=2Y 8 ================================================ FILE: test/synthetic/0x01/mix_3_mpt_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mpt_mline_mpoly.mlt ================================================ Olayer1P0123B"" 2 8u$$$ 7S + f@ ================================================ FILE: test/synthetic/0x01/mix_3_mpt_mline_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mpt_mline_mpt.mlt ================================================ :layer1P012B 2 8u$$$ 9I 8 ================================================ FILE: test/synthetic/0x01/mix_3_mpt_mpoly_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mpt_mpoly_mpt.mlt ================================================ Jlayer1P0123B 2 8a + f@M 8 ================================================ FILE: test/synthetic/0x01/mix_3_mpt_poly_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mpt_poly_mpt.mlt ================================================ :layer1P0123B 2 8@."  8 ================================================ FILE: test/synthetic/0x01/mix_3_mpt_polyh_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_mpt_polyh_mpt.mlt ================================================ @layer1P0123B 2 8:CK(\"7+ #9 8 ================================================ FILE: test/synthetic/0x01/mix_3_mpt_pt_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_poly_line_poly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_poly_line_poly.mlt ================================================ 8layer1P023Bn ."  2\." ================================================ FILE: test/synthetic/0x01/mix_3_poly_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_poly_mline_mpoly.mlt ================================================ Player1P0123B""n ." e$$$ 7S + f@ ================================================ FILE: test/synthetic/0x01/mix_3_poly_mline_poly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_poly_mline_poly.mlt ================================================ @layer1P0123Bn ." e$$$ (q." ================================================ FILE: test/synthetic/0x01/mix_3_poly_mpoly_poly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_poly_mpoly_poly.mlt ================================================ Llayer1P0123Bn ."  + f@#u." ================================================ FILE: test/synthetic/0x01/mix_3_poly_mpoly_poly_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_poly_mpt_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_poly_mpt_mline.mlt ================================================ @layer1P0123Bn ."  8u$$$  ================================================ FILE: test/synthetic/0x01/mix_3_poly_mpt_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_poly_mpt_mpoly.mlt ================================================ Klayer1P0123Bn ."  8a + f@ ================================================ FILE: test/synthetic/0x01/mix_3_poly_mpt_poly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_poly_mpt_poly.mlt ================================================ ;layer1P0123Bn ."  8@." ================================================ FILE: test/synthetic/0x01/mix_3_poly_polyh_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_poly_polyh_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_poly_polyh_mpoly.mlt ================================================ Rlayer1P0123B$$n ." -K(\"7+ !C + f@ ================================================ FILE: test/synthetic/0x01/mix_3_poly_polyh_mpoly_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_poly_polyh_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_poly_polyh_mpt.mlt ================================================ Alayer1P0123Bn ." -K(\"7+ #9 8 ================================================ FILE: test/synthetic/0x01/mix_3_poly_polyh_poly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_poly_polyh_poly.mlt ================================================ >layer1P023Bn ." -K(\"7+ >a." ================================================ FILE: test/synthetic/0x01/mix_3_poly_polyh_poly_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_poly_pt_poly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_polyh_line_polyh.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_polyh_line_polyh.mlt ================================================ Dlayer1P023BhFK(\"7+ %2VEK(\"7+ ================================================ FILE: test/synthetic/0x01/mix_3_polyh_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_polyh_mline_polyh.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_polyh_mpoly_polyh.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_polyh_mpoly_polyh.mlt ================================================ Ylayer1P0123B**hFK(\"7+ !C + f@)9K(\"7+ ================================================ FILE: test/synthetic/0x01/mix_3_polyh_mpoly_polyh_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_polyh_mpt_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_polyh_mpt_mline.mlt ================================================ Flayer1P0123BhFK(\"7+ #9 8u$$$  ================================================ FILE: test/synthetic/0x01/mix_3_polyh_mpt_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_polyh_mpt_mpoly.mlt ================================================ Qlayer1P0123B$$hFK(\"7+ #9 8a + f@ ================================================ FILE: test/synthetic/0x01/mix_3_polyh_mpt_polyh.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_polyh_mpt_polyh.mlt ================================================ Hlayer1P0123BhFK(\"7+ #9 8:CK(\"7+ ================================================ FILE: test/synthetic/0x01/mix_3_polyh_poly_polyh.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_polyh_poly_polyh.mlt ================================================ Elayer1P023BhFK(\"7+ >a." -K(\"7+ ================================================ FILE: test/synthetic/0x01/mix_3_polyh_poly_polyh_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_polyh_pt_polyh.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_line_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_line_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_line_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_line_poly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_line_polyh.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_line_pt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_mline_pt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_mpoly_pt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_mpt_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_mpt_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_mpt_pt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_poly_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_poly_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_poly_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_poly_polyh.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_poly_pt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_polyh_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_polyh_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_polyh_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_3_pt_polyh_pt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_line_mpt_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_line_mpt_mline_mpoly.mlt ================================================ Wlayer1P0123B(( L2Y 8u$$$ 7S + f@ ================================================ FILE: test/synthetic/0x01/mix_4_line_poly_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_line_poly_mline_mpoly.mlt ================================================ Ylayer1P0123B() L2\." e$$$ 7S + f@ ================================================ FILE: test/synthetic/0x01/mix_4_line_poly_mpt_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_line_poly_mpt_mline.mlt ================================================ Ilayer1P0123B L2\."  8u$$$  ================================================ FILE: test/synthetic/0x01/mix_4_line_poly_mpt_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_line_poly_mpt_mpoly.mlt ================================================ Tlayer1P0123B$& L2\."  8a + f@ ================================================ FILE: test/synthetic/0x01/mix_4_line_poly_polyh_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_line_poly_polyh_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_line_poly_polyh_mpoly.mlt ================================================ [layer1P0123B*+ L2\." -K(\"7+ !C + f@ ================================================ FILE: test/synthetic/0x01/mix_4_line_poly_polyh_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_line_poly_polyh_mpt.mlt ================================================ Jlayer1P0123B L2\." -K(\"7+ #9 8 ================================================ FILE: test/synthetic/0x01/mix_4_line_polyh_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_line_polyh_mpt_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_line_polyh_mpt_mline.mlt ================================================ Nlayer1P0123B"" L2VEK(\"7+ #9 8u$$$  ================================================ FILE: test/synthetic/0x01/mix_4_line_polyh_mpt_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_line_polyh_mpt_mpoly.mlt ================================================ Ylayer1P0123B** L2VEK(\"7+ #9 8a + f@ ================================================ FILE: test/synthetic/0x01/mix_4_poly_mpt_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_poly_mpt_mline_mpoly.mlt ================================================ Ylayer1P0123B()n ."  8u$$$ 7S + f@ ================================================ FILE: test/synthetic/0x01/mix_4_poly_polyh_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_poly_polyh_mpt_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_poly_polyh_mpt_mline.mlt ================================================ Olayer1P0123B""n ." -K(\"7+ #9 8u$$$  ================================================ FILE: test/synthetic/0x01/mix_4_poly_polyh_mpt_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_poly_polyh_mpt_mpoly.mlt ================================================ Zlayer1P0123B**n ." -K(\"7+ #9 8a + f@ ================================================ FILE: test/synthetic/0x01/mix_4_polyh_mpt_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_polyh_mpt_mline_mpoly.mlt ================================================ _layer1P0123B..hFK(\"7+ #9 8u$$$ 7S + f@ ================================================ FILE: test/synthetic/0x01/mix_4_pt_line_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_pt_line_mpt_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_pt_line_mpt_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_pt_line_poly_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_pt_line_poly_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_pt_line_poly_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_pt_line_poly_polyh.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_pt_line_polyh_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_pt_line_polyh_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_pt_line_polyh_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_pt_mpt_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_pt_poly_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_pt_poly_mpt_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_pt_poly_mpt_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_pt_poly_polyh_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_pt_poly_polyh_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_pt_poly_polyh_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_pt_polyh_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_pt_polyh_mpt_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_4_pt_polyh_mpt_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_line_poly_mpt_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_line_poly_mpt_mline_mpoly.mlt ================================================ blayer1P0123B.0 L2\."  8u$$$ 7S + f@ ================================================ FILE: test/synthetic/0x01/mix_5_line_poly_polyh_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_line_poly_polyh_mpt_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_line_poly_polyh_mpt_mline.mlt ================================================ Xlayer1P0123B() L2\." -K(\"7+ #9 8u$$$  ================================================ FILE: test/synthetic/0x01/mix_5_line_poly_polyh_mpt_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_line_poly_polyh_mpt_mpoly.mlt ================================================ clayer1P0123B01 L2\." -K(\"7+ #9 8a + f@ ================================================ FILE: test/synthetic/0x01/mix_5_line_polyh_mpt_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_line_polyh_mpt_mline_mpoly.mlt ================================================ glayer1P0123B44 L2VEK(\"7+ #9 8u$$$ 7S + f@ ================================================ FILE: test/synthetic/0x01/mix_5_poly_polyh_mpt_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_poly_polyh_mpt_mline_mpoly.mlt ================================================ hlayer1P0123B44n ." -K(\"7+ #9 8u$$$ 7S + f@ ================================================ FILE: test/synthetic/0x01/mix_5_pt_line_mpt_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_pt_line_poly_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_pt_line_poly_mpt_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_pt_line_poly_mpt_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_pt_line_poly_polyh_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_pt_line_poly_polyh_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_pt_line_poly_polyh_mpt.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_pt_line_polyh_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_pt_line_polyh_mpt_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_pt_line_polyh_mpt_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_pt_poly_mpt_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_pt_poly_polyh_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_pt_poly_polyh_mpt_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_pt_poly_polyh_mpt_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_5_pt_polyh_mpt_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_6_line_poly_polyh_mpt_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_6_line_poly_polyh_mpt_mline_mpoly.mlt ================================================ qlayer1P0123 B:; L2\." -K(\"7+ #9 8u$$$ 7S + f@ ================================================ FILE: test/synthetic/0x01/mix_6_pt_line_poly_mpt_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_6_pt_line_poly_polyh_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_6_pt_line_poly_polyh_mpt_mline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_6_pt_line_poly_polyh_mpt_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_6_pt_line_polyh_mpt_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_6_pt_poly_polyh_mpt_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/mix_7_pt_line_poly_polyh_mpt_mline_mpoly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 38, 29 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "LineString", "coordinates": [ [ 5, 38 ], [ 12, 45 ], [ 9, 70 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 6, 25 ], [ 21, 41 ], [ 23, 69 ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 24, 10 ], [ 42, 18 ] ], [ [ 30, 36 ], [ 48, 52 ], [ 35, 62 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 7, 20 ], [ 21, 31 ], [ 26, 9 ], [ 7, 20 ] ], [ [ 15, 20 ], [ 20, 15 ], [ 18, 25 ], [ 15, 20 ] ] ], [ [ [ 69, 57 ], [ 71, 66 ], [ 73, 64 ], [ 69, 57 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/multiline.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 11, 52 ], [ 71, 72 ], [ 61, 22 ] ], [ [ 23, 34 ], [ 73, 4 ], [ 13, 24 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/multiline.mlt ================================================ .layer1P012bB hx(cKd;w( ================================================ FILE: test/synthetic/0x01/multiline_morton.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 0, 0 ], [ 8, 0 ], [ 0, 8 ], [ 8, 8 ], [ 16, 0 ], [ 24, 0 ], [ 16, 8 ], [ 24, 8 ] ], [ [ 0, 16 ], [ 8, 16 ], [ 0, 24 ], [ 8, 24 ], [ 16, 16 ], [ 24, 16 ], [ 16, 24 ], [ 24, 24 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/multipoint.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 11, 52 ], [ 71, 72 ], [ 61, 22 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/multipoint.mlt ================================================ layer1P01Bhx(c ================================================ FILE: test/synthetic/0x01/multipoint_morton.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPoint", "coordinates": [ [ 0, 0 ], [ 8, 0 ], [ 0, 8 ], [ 8, 8 ], [ 16, 0 ], [ 24, 0 ], [ 16, 8 ], [ 24, 8 ] ] } } ] } ================================================ FILE: test/synthetic/0x01/point.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/poly.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 11, 52 ], [ 71, 72 ], [ 61, 22 ], [ 11, 52 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly.mlt ================================================ %layer1P023Bhx(c ================================================ FILE: test/synthetic/0x01/poly_collinear.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 10, 0 ], [ 20, 0 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_collinear_fpf.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 10, 0 ], [ 20, 0 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_collinear_fpf_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 10, 0 ], [ 20, 0 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_collinear_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 10, 0 ], [ 20, 0 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_fpf.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 11, 52 ], [ 71, 72 ], [ 61, 22 ], [ 11, 52 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_fpf_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 11, 52 ], [ 71, 72 ], [ 61, 22 ], [ 11, 52 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_hole.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 11, 52 ], [ 71, 72 ], [ 61, 22 ], [ 11, 52 ] ], [ [ 65, 66 ], [ 35, 56 ], [ 55, 36 ], [ 65, 66 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_hole.mlt ================================================ .layer1P023bB hx(cX;(' ================================================ FILE: test/synthetic/0x01/poly_hole_fpf.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 11, 52 ], [ 71, 72 ], [ 61, 22 ], [ 11, 52 ] ], [ [ 65, 66 ], [ 35, 56 ], [ 55, 36 ], [ 65, 66 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_hole_fpf_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 11, 52 ], [ 71, 72 ], [ 61, 22 ], [ 11, 52 ] ], [ [ 65, 66 ], [ 35, 56 ], [ 55, 36 ], [ 65, 66 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_hole_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 11, 52 ], [ 71, 72 ], [ 61, 22 ], [ 11, 52 ] ], [ [ 65, 66 ], [ 35, 56 ], [ 55, 36 ], [ 65, 66 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_hole_touching.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 10, 0 ], [ 10, 10 ], [ 0, 10 ], [ 0, 0 ] ], [ [ 0, 0 ], [ 2, 2 ], [ 5, 2 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_hole_touching_fpf.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 10, 0 ], [ 10, 10 ], [ 0, 10 ], [ 0, 0 ] ], [ [ 0, 0 ], [ 2, 2 ], [ 5, 2 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_hole_touching_fpf_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 10, 0 ], [ 10, 10 ], [ 0, 10 ], [ 0, 0 ] ], [ [ 0, 0 ], [ 2, 2 ], [ 5, 2 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_hole_touching_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 10, 0 ], [ 10, 10 ], [ 0, 10 ], [ 0, 0 ] ], [ [ 0, 0 ], [ 2, 2 ], [ 5, 2 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_morton_hole_morton.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 8, 0 ], [ 0, 8 ], [ 8, 8 ], [ 0, 0 ] ], [ [ 16, 0 ], [ 24, 0 ], [ 16, 8 ], [ 24, 8 ], [ 0, 16 ], [ 8, 16 ], [ 0, 24 ], [ 8, 24 ], [ 16, 16 ], [ 24, 16 ], [ 16, 24 ], [ 24, 24 ], [ 16, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_morton_ring_morton.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 8, 0 ], [ 0, 8 ], [ 8, 8 ], [ 16, 0 ], [ 24, 0 ], [ 16, 8 ], [ 24, 8 ], [ 0, 16 ], [ 8, 16 ], [ 0, 24 ], [ 8, 24 ], [ 16, 16 ], [ 24, 16 ], [ 16, 24 ], [ 24, 24 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_morton_ring_no_morton.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 8, 0 ], [ 0, 8 ], [ 8, 8 ], [ 16, 0 ], [ 24, 0 ], [ 16, 8 ], [ 24, 8 ], [ 0, 16 ], [ 8, 16 ], [ 0, 24 ], [ 8, 24 ], [ 16, 16 ], [ 24, 16 ], [ 16, 24 ], [ 24, 24 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_multi.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 11, 52 ], [ 71, 72 ], [ 61, 22 ], [ 11, 52 ] ] ], [ [ [ 23, 34 ], [ 73, 4 ], [ 13, 24 ], [ 23, 34 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_multi.mlt ================================================ 6layer1P012b3bB hx(cKd;w( ================================================ FILE: test/synthetic/0x01/poly_multi_fpf.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 11, 52 ], [ 71, 72 ], [ 61, 22 ], [ 11, 52 ] ] ], [ [ [ 23, 34 ], [ 73, 4 ], [ 13, 24 ], [ 23, 34 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_multi_fpf_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 11, 52 ], [ 71, 72 ], [ 61, 22 ], [ 11, 52 ] ] ], [ [ [ 23, 34 ], [ 73, 4 ], [ 13, 24 ], [ 23, 34 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_multi_morton_hole_morton.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 0, 0 ], [ 8, 0 ], [ 0, 8 ], [ 8, 8 ], [ 0, 0 ] ], [ [ 16, 0 ], [ 24, 0 ], [ 16, 8 ], [ 24, 8 ], [ 0, 16 ], [ 8, 16 ], [ 0, 24 ], [ 8, 24 ], [ 16, 16 ], [ 24, 16 ], [ 16, 24 ], [ 24, 24 ], [ 16, 0 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_multi_morton_ring_morton.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 0, 0 ], [ 8, 0 ], [ 0, 8 ], [ 8, 8 ], [ 16, 0 ], [ 24, 0 ], [ 16, 8 ], [ 24, 8 ], [ 0, 0 ] ] ], [ [ [ 0, 16 ], [ 8, 16 ], [ 0, 24 ], [ 8, 24 ], [ 16, 16 ], [ 24, 16 ], [ 16, 24 ], [ 24, 24 ], [ 0, 16 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_multi_morton_ring_no_morton.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 0, 0 ], [ 8, 0 ], [ 0, 8 ], [ 8, 8 ], [ 16, 0 ], [ 24, 0 ], [ 16, 8 ], [ 24, 8 ], [ 0, 0 ] ] ], [ [ [ 0, 16 ], [ 8, 16 ], [ 0, 24 ], [ 8, 24 ], [ 16, 16 ], [ 24, 16 ], [ 16, 24 ], [ 24, 24 ], [ 0, 16 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_multi_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 11, 52 ], [ 71, 72 ], [ 61, 22 ], [ 11, 52 ] ] ], [ [ [ 23, 34 ], [ 73, 4 ], [ 13, 24 ], [ 23, 34 ] ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_self_intersect.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 10, 10 ], [ 0, 10 ], [ 10, 0 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_self_intersect_fpf.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 10, 10 ], [ 0, 10 ], [ 10, 0 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_self_intersect_fpf_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 10, 10 ], [ 0, 10 ], [ 10, 0 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_self_intersect_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 10, 10 ], [ 0, 10 ], [ 10, 0 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/poly_tes.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 11, 52 ], [ 71, 72 ], [ 61, 22 ], [ 11, 52 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_bool.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": true }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_bool_false.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": false }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_bool_false_null.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": false }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_bool_null_false.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": false }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_bool_null_true.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": true }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_bool_true_null.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": true }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_empty_name.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "": true, "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f32.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 3.14 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f32_max.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 3.4028235E38 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f32_min_norm.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1.1754944E-38 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f32_min_val.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1.4E-45 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f32_nan.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "f32::NAN" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f32_neg_inf.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "f32::NEG_INFINITY" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f32_neg_zero.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": -0.0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f32_null_val.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 3.14 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f32_pos_inf.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "f32::INFINITY" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f32_val_null.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 3.14 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f32_zero.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f64.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 3.141592653589793 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f64_max.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1.7976931348623157E308 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f64_min_norm.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2.2250738585072014E-308 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f64_min_val.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 4.9E-324 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f64_nan.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "f64::NAN" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f64_neg_inf.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "f64::NEG_INFINITY" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f64_neg_zero.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": -0.0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f64_null_val.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 3.141592653589793 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f64_pos_inf.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "f64::INFINITY" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f64_val_null.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 3.141592653589793 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_f64_zero.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_i32.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_i32_max.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2147483647 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_i32_min.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": -2147483648 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_i32_neg.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": -42 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_i32_null_val.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_i32_val_null.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_i64.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_i64_max.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9223372036854775807 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_i64_min.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": -9223372036854775808 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_i64_neg.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": -9876543210 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_i64_null_val.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_i64_val_null.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_special_name.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "hello\u0000 world\n": true }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_str_ascii.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "42" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_str_empty.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_str_empty_val.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_str_escape.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "Line1\n\t\"quoted\"\\path" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_str_null_val.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "42" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_str_special.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "hello\u0000 world\n" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_str_unicode.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "München 📍 café" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_str_val_empty.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_str_val_null.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "42" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_u32.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_u32_max.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 4294967295 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_u32_min.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_u32_null_val.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_u32_val_null.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_u64.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "bignum": 1234567890123456789 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_u64_max.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "bignum": 18446744073709551615 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_u64_min.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "bignum": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_u64_null_val.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1234567890123456789 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/prop_u64_val_null.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1234567890123456789 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_i32.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_i32_delta.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_i32_delta_rle.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_i32_rle.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_i64.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_i64_delta.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_i64_delta_rle.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_i64_rle.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_mixed.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "active": true, "biggest": 0, "bignum": 42, "count": 42, "medium": 100, "name": "Test Point", "precision": 0.123456789, "temp": 25.5 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_no_shared_dict.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "name:de": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "name:en": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_offset_str.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_offset_str_fsst.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_shared_dict.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "name:de": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "name:en": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_shared_dict_2_same_prefix.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "name:de": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "name:he": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "name_en": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "name_fr": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_shared_dict_fsst.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "name:de": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "name:en": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_shared_dict_no_child_name.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_shared_dict_no_child_name_fsst.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_shared_dict_no_struct_name.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_shared_dict_no_struct_name_fsst.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_shared_dict_one_child.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "name:en": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "place": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_shared_dict_one_child_fsst.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "name:en": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "place": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_str.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "residential_zone_north_sector_1" }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "commercial_zone_south_sector_2" }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "industrial_zone_east_sector_3" }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "park_zone_west_sector_4" }, "geometry": { "type": "Point", "coordinates": [ 65, 66 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "water_zone_north_sector_5" }, "geometry": { "type": "Point", "coordinates": [ 35, 56 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "residential_zone_south_sector_6" }, "geometry": { "type": "Point", "coordinates": [ 55, 36 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_str_fsst.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "residential_zone_north_sector_1" }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "commercial_zone_south_sector_2" }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "industrial_zone_east_sector_3" }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "park_zone_west_sector_4" }, "geometry": { "type": "Point", "coordinates": [ 65, 66 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "water_zone_north_sector_5" }, "geometry": { "type": "Point", "coordinates": [ 35, 56 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "residential_zone_south_sector_6" }, "geometry": { "type": "Point", "coordinates": [ 55, 36 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u32.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u32_delta.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u32_delta_rle.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u32_fpf_127.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u32_fpf_128.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u32_fpf_129.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u32_fpf_255.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u32_fpf_256.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u32_fpf_257.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u32_fpf_383.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u32_fpf_384.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u32_fpf_385.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u32_fpf_511.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u32_fpf_512.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u32_fpf_513.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u32_rle.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u64.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u64_delta.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u64_delta_rle.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01/props_u64_rle.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/id64_max.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 18446744073709551615, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/id_max.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 4294967295, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/ids64_minmax.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 0, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 18446744073709551615, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 0, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 18446744073709551615, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/ids64_minmax_delta.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 0, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 18446744073709551615, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 0, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 18446744073709551615, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/ids_delta_fpf.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/ids_fpf.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "id": 103, "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/mix_2_poly_poly_tes_ns.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/mix_2_poly_polyh_tes_ns.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/mix_2_polyh_polyh_tes_ns.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/mix_3_poly_polyh_poly_tes_ns.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/mix_3_polyh_poly_polyh_tes_ns.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 55, 5 ], [ 58, 28 ], [ 75, 22 ], [ 55, 5 ] ] ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 52, 35 ], [ 14, 55 ], [ 60, 72 ], [ 52, 35 ] ], [ [ 32, 50 ], [ 36, 60 ], [ 24, 54 ], [ 32, 50 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/poly_collinear_fpf_tes_ns.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 10, 0 ], [ 20, 0 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/poly_collinear_tes_ns.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 10, 0 ], [ 20, 0 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/poly_fpf_tes_ns.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 11, 52 ], [ 71, 72 ], [ 61, 22 ], [ 11, 52 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/poly_hole_fpf_tes_ns.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 11, 52 ], [ 71, 72 ], [ 61, 22 ], [ 11, 52 ] ], [ [ 65, 66 ], [ 35, 56 ], [ 55, 36 ], [ 65, 66 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/poly_hole_tes_ns.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 11, 52 ], [ 71, 72 ], [ 61, 22 ], [ 11, 52 ] ], [ [ 65, 66 ], [ 35, 56 ], [ 55, 36 ], [ 65, 66 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/poly_hole_touching_fpf_tes_ns.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 10, 0 ], [ 10, 10 ], [ 0, 10 ], [ 0, 0 ] ], [ [ 0, 0 ], [ 2, 2 ], [ 5, 2 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/poly_hole_touching_tes_ns.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 10, 0 ], [ 10, 10 ], [ 0, 10 ], [ 0, 0 ] ], [ [ 0, 0 ], [ 2, 2 ], [ 5, 2 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/poly_self_intersect_fpf_tes_ns.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 10, 10 ], [ 0, 10 ], [ 10, 0 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/poly_self_intersect_tes_ns.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0, 0 ], [ 10, 10 ], [ 0, 10 ], [ 10, 0 ], [ 0, 0 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/poly_tes_ns.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 11, 52 ], [ 71, 72 ], [ 61, 22 ], [ 11, 52 ] ] ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_bool_false_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": false }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_bool_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": true }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_empty_name_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "": true, "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_f32_max_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 3.4028234663852886e+38 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_f32_min_norm_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1.1754943508222875e-38 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_f32_min_val_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1.401298464324817e-45 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_f32_nan_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "f32::NAN" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_f32_neg_inf_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "f32::NEG_INFINITY" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_f32_neg_zero_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": -0.0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_f32_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 3.140000104904175 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_f32_pos_inf_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "f32::INFINITY" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_f32_zero_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_f64_max_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1.7976931348623157e+308 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_f64_min_norm_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2.2250738585072014e-308 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_f64_min_val_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 5e-324 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_f64_nan_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "f64::NAN" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_f64_neg_inf_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "f64::NEG_INFINITY" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_f64_neg_zero_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": -0.0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_f64_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 3.141592653589793 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_f64_pos_inf_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "f64::INFINITY" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_f64_zero_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_i32_max_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2147483647 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_i32_min_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": -2147483648 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_i32_neg_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": -42 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_i32_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_i64_max_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9223372036854775807 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_i64_min_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": -9223372036854775808 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_i64_neg_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": -9876543210 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_i64_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9876543210 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_special_name_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "hello\u0000 world\n": true }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_str_ascii_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "42" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_str_empty_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_str_escape_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "Line1\n\t\"quoted\"\\path" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_str_special_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "hello\u0000 world\n" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_str_unicode_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "München 📍 café" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_u32_max_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 4294967295 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_u32_min_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_u32_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_u64_max_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "bignum": 18446744073709551615 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_u64_min_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "bignum": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/prop_u64_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "bignum": 1234567890123456789 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_i32_delta_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_i32_delta_rle_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_i32_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_i32_rle_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 42 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_mixed_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "active": true, "biggest": 0, "bignum": 42, "count": 42, "medium": 100, "name": "Test Point", "precision": 0.123456789, "temp": 25.5 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_no_shared_dict_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "name:de": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "name:en": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_offset_str_fsst.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_offset_str_fsst_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_offset_str_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_shared_dict_2_same_prefix_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "name:de": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "name:he": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "name_en": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "name_fr": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_shared_dict_fsst.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "name:de": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "name:en": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_shared_dict_fsst_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "name:de": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "name:en": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_shared_dict_no_child_name_fsst.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_shared_dict_no_child_name_fsst_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_shared_dict_no_child_name_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_shared_dict_no_struct_name_fsst.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_shared_dict_no_struct_name_fsst_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_shared_dict_no_struct_name_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_shared_dict_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "name:de": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "name:en": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_shared_dict_one_child_fsst.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "name:en": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "place": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_shared_dict_one_child_fsst_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "name:en": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "place": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_shared_dict_one_child_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "name:en": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "place": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_shared_dict_presence_variants.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "1-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "1-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "2-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "2-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "3-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "4-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "4-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "5-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "5-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "6-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "7-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "8-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "1-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "1-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "2-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "3-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "4-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "7-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "1-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "1-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "2-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "2-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "3-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "4-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "4-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "5-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "5-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "6-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "7-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "8-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_shared_dict_presence_variants_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "1-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "1-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "2-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "2-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "3-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "4-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "4-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "5-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "5-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "6-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "7-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "8-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "1-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "1-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "2-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "3-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "4-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "7-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "1-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "1-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "2-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "2-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "3-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "4-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "4-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "5-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "5-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "6-a": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "7-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "8-b": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "_extent": 80, "_layer": "layer1" }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_str_fsst.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "residential_zone_north_sector_1" }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "commercial_zone_south_sector_2" }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "industrial_zone_east_sector_3" }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "park_zone_west_sector_4" }, "geometry": { "type": "Point", "coordinates": [ 65, 66 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "water_zone_north_sector_5" }, "geometry": { "type": "Point", "coordinates": [ 35, 56 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "residential_zone_south_sector_6" }, "geometry": { "type": "Point", "coordinates": [ 55, 36 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_str_fsst_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "residential_zone_north_sector_1" }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "commercial_zone_south_sector_2" }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "industrial_zone_east_sector_3" }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "park_zone_west_sector_4" }, "geometry": { "type": "Point", "coordinates": [ 65, 66 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "water_zone_north_sector_5" }, "geometry": { "type": "Point", "coordinates": [ 35, 56 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "residential_zone_south_sector_6" }, "geometry": { "type": "Point", "coordinates": [ 55, 36 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_str_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "residential_zone_north_sector_1" }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "commercial_zone_south_sector_2" }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "industrial_zone_east_sector_3" }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "park_zone_west_sector_4" }, "geometry": { "type": "Point", "coordinates": [ 65, 66 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "water_zone_north_sector_5" }, "geometry": { "type": "Point", "coordinates": [ 35, 56 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": "residential_zone_south_sector_6" }, "geometry": { "type": "Point", "coordinates": [ 55, 36 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u32_delta_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u32_delta_rle_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u32_fpf_127_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u32_fpf_128_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u32_fpf_129_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u32_fpf_255_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u32_fpf_256_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u32_fpf_257_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u32_fpf_383_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u32_fpf_384_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u32_fpf_385_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u32_fpf_511_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u32_fpf_512_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u32_fpf_513_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 0 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 1 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 2 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u32_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u32_rle_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u64_delta_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u64_delta_rle_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u64_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/0x01-rust/props_u64_rle_np.json ================================================ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 13, 42 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 11, 52 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 71, 72 ] } }, { "type": "Feature", "properties": { "_extent": 80, "_layer": "layer1", "val": 9000 }, "geometry": { "type": "Point", "coordinates": [ 61, 22 ] } } ] } ================================================ FILE: test/synthetic/java.txt ================================================ mix_2_line_mline--Data(Vertex) ComponentwiseDelta mix_2_line_mline--Length(Geometries) None mix_2_line_mline--Length(Parts) None mix_2_line_mline--Length(VarBinary) None mix_2_line_mpoly--Data(Vertex) ComponentwiseDelta mix_2_line_mpoly--Length(Geometries) None mix_2_line_mpoly--Length(Parts) None mix_2_line_mpoly--Length(Rings) Rle mix_2_line_mpoly--Length(VarBinary) None mix_2_line_mpt--Data(Vertex) ComponentwiseDelta mix_2_line_mpt--Length(Geometries) None mix_2_line_mpt--Length(Parts) None mix_2_line_mpt--Length(VarBinary) None mix_2_line_poly--Data(Vertex) ComponentwiseDelta mix_2_line_poly--Length(Parts) None mix_2_line_poly--Length(Rings) Rle mix_2_line_poly--Length(VarBinary) None mix_2_line_polyh--Data(Vertex) ComponentwiseDelta mix_2_line_polyh--Length(Parts) None mix_2_line_polyh--Length(Rings) Rle mix_2_line_polyh--Length(VarBinary) None mix_2_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_2_mline_mpoly--Length(Geometries) Rle mix_2_mline_mpoly--Length(Parts) None mix_2_mline_mpoly--Length(Rings) Rle mix_2_mline_mpoly--Length(VarBinary) None mix_2_mpt_mline--Data(Vertex) ComponentwiseDelta mix_2_mpt_mline--Length(Geometries) None mix_2_mpt_mline--Length(Parts) None mix_2_mpt_mline--Length(VarBinary) None mix_2_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_2_mpt_mpoly--Length(Geometries) None mix_2_mpt_mpoly--Length(Parts) None mix_2_mpt_mpoly--Length(Rings) Rle mix_2_mpt_mpoly--Length(VarBinary) None mix_2_poly_mline--Data(Vertex) ComponentwiseDelta mix_2_poly_mline--Length(Geometries) None mix_2_poly_mline--Length(Parts) None mix_2_poly_mline--Length(Rings) None mix_2_poly_mline--Length(VarBinary) None mix_2_poly_mpoly--Data(Vertex) ComponentwiseDelta mix_2_poly_mpoly--Length(Geometries) None mix_2_poly_mpoly--Length(Parts) None mix_2_poly_mpoly--Length(Rings) Rle mix_2_poly_mpoly--Length(VarBinary) None mix_2_poly_mpt--Data(Vertex) ComponentwiseDelta mix_2_poly_mpt--Length(Geometries) None mix_2_poly_mpt--Length(Parts) None mix_2_poly_mpt--Length(Rings) None mix_2_poly_mpt--Length(VarBinary) None mix_2_poly_polyh--Data(Vertex) ComponentwiseDelta mix_2_poly_polyh--Length(Parts) None mix_2_poly_polyh--Length(Rings) Rle mix_2_poly_polyh--Length(VarBinary) Rle mix_2_polyh_mline--Data(Vertex) ComponentwiseDelta mix_2_polyh_mline--Length(Geometries) None mix_2_polyh_mline--Length(Parts) None mix_2_polyh_mline--Length(Rings) None mix_2_polyh_mline--Length(VarBinary) None mix_2_polyh_mpoly--Data(Vertex) ComponentwiseDelta mix_2_polyh_mpoly--Length(Geometries) None mix_2_polyh_mpoly--Length(Parts) None mix_2_polyh_mpoly--Length(Rings) Rle mix_2_polyh_mpoly--Length(VarBinary) None mix_2_polyh_mpt--Data(Vertex) ComponentwiseDelta mix_2_polyh_mpt--Length(Geometries) None mix_2_polyh_mpt--Length(Parts) None mix_2_polyh_mpt--Length(Rings) Rle mix_2_polyh_mpt--Length(VarBinary) None mix_2_pt_line--Data(Vertex) ComponentwiseDelta mix_2_pt_line--Length(Parts) None mix_2_pt_line--Length(VarBinary) None mix_2_pt_mline--Data(Vertex) ComponentwiseDelta mix_2_pt_mline--Length(Geometries) None mix_2_pt_mline--Length(Parts) None mix_2_pt_mline--Length(VarBinary) None mix_2_pt_mpoly--Data(Vertex) ComponentwiseDelta mix_2_pt_mpoly--Length(Geometries) None mix_2_pt_mpoly--Length(Parts) None mix_2_pt_mpoly--Length(Rings) Rle mix_2_pt_mpoly--Length(VarBinary) None mix_2_pt_mpt--Data(Vertex) ComponentwiseDelta mix_2_pt_mpt--Length(Geometries) None mix_2_pt_mpt--Length(VarBinary) None mix_2_pt_poly--Data(Vertex) ComponentwiseDelta mix_2_pt_poly--Length(Parts) None mix_2_pt_poly--Length(Rings) None mix_2_pt_poly--Length(VarBinary) None mix_2_pt_polyh--Data(Vertex) ComponentwiseDelta mix_2_pt_polyh--Length(Parts) None mix_2_pt_polyh--Length(Rings) Rle mix_2_pt_polyh--Length(VarBinary) None mix_3_line_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_3_line_mline_mpoly--Length(Geometries) Rle mix_3_line_mline_mpoly--Length(Parts) None mix_3_line_mline_mpoly--Length(Rings) None mix_3_line_mline_mpoly--Length(VarBinary) None mix_3_line_mpt_mline--Data(Vertex) ComponentwiseDelta mix_3_line_mpt_mline--Length(Geometries) None mix_3_line_mpt_mline--Length(Parts) None mix_3_line_mpt_mline--Length(VarBinary) None mix_3_line_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_3_line_mpt_mpoly--Length(Geometries) None mix_3_line_mpt_mpoly--Length(Parts) None mix_3_line_mpt_mpoly--Length(Rings) Rle mix_3_line_mpt_mpoly--Length(VarBinary) None mix_3_line_poly_mline--Data(Vertex) ComponentwiseDelta mix_3_line_poly_mline--Length(Geometries) None mix_3_line_poly_mline--Length(Parts) None mix_3_line_poly_mline--Length(Rings) None mix_3_line_poly_mline--Length(VarBinary) None mix_3_line_poly_mpoly--Data(Vertex) ComponentwiseDelta mix_3_line_poly_mpoly--Length(Geometries) None mix_3_line_poly_mpoly--Length(Parts) None mix_3_line_poly_mpoly--Length(Rings) Rle mix_3_line_poly_mpoly--Length(VarBinary) None mix_3_line_poly_mpt--Data(Vertex) ComponentwiseDelta mix_3_line_poly_mpt--Length(Geometries) None mix_3_line_poly_mpt--Length(Parts) None mix_3_line_poly_mpt--Length(Rings) Rle mix_3_line_poly_mpt--Length(VarBinary) DeltaRle mix_3_line_poly_polyh--Data(Vertex) ComponentwiseDelta mix_3_line_poly_polyh--Length(Parts) None mix_3_line_poly_polyh--Length(Rings) Rle mix_3_line_poly_polyh--Length(VarBinary) None mix_3_line_polyh_mline--Data(Vertex) ComponentwiseDelta mix_3_line_polyh_mline--Length(Geometries) None mix_3_line_polyh_mline--Length(Parts) None mix_3_line_polyh_mline--Length(Rings) None mix_3_line_polyh_mline--Length(VarBinary) None mix_3_line_polyh_mpoly--Data(Vertex) ComponentwiseDelta mix_3_line_polyh_mpoly--Length(Geometries) None mix_3_line_polyh_mpoly--Length(Parts) None mix_3_line_polyh_mpoly--Length(Rings) Rle mix_3_line_polyh_mpoly--Length(VarBinary) None mix_3_line_polyh_mpt--Data(Vertex) ComponentwiseDelta mix_3_line_polyh_mpt--Length(Geometries) None mix_3_line_polyh_mpt--Length(Parts) None mix_3_line_polyh_mpt--Length(Rings) Rle mix_3_line_polyh_mpt--Length(VarBinary) DeltaRle mix_3_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_3_mpt_mline_mpoly--Length(Geometries) None mix_3_mpt_mline_mpoly--Length(Parts) None mix_3_mpt_mline_mpoly--Length(Rings) Rle mix_3_mpt_mline_mpoly--Length(VarBinary) None mix_3_poly_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_3_poly_mline_mpoly--Length(Geometries) Rle mix_3_poly_mline_mpoly--Length(Parts) None mix_3_poly_mline_mpoly--Length(Rings) None mix_3_poly_mline_mpoly--Length(VarBinary) None mix_3_poly_mpt_mline--Data(Vertex) ComponentwiseDelta mix_3_poly_mpt_mline--Length(Geometries) None mix_3_poly_mpt_mline--Length(Parts) None mix_3_poly_mpt_mline--Length(Rings) None mix_3_poly_mpt_mline--Length(VarBinary) None mix_3_poly_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_3_poly_mpt_mpoly--Length(Geometries) None mix_3_poly_mpt_mpoly--Length(Parts) None mix_3_poly_mpt_mpoly--Length(Rings) Rle mix_3_poly_mpt_mpoly--Length(VarBinary) None mix_3_poly_polyh_mline--Data(Vertex) ComponentwiseDelta mix_3_poly_polyh_mline--Length(Geometries) None mix_3_poly_polyh_mline--Length(Parts) None mix_3_poly_polyh_mline--Length(Rings) None mix_3_poly_polyh_mline--Length(VarBinary) None mix_3_poly_polyh_mpoly--Data(Vertex) ComponentwiseDelta mix_3_poly_polyh_mpoly--Length(Geometries) None mix_3_poly_polyh_mpoly--Length(Parts) None mix_3_poly_polyh_mpoly--Length(Rings) Rle mix_3_poly_polyh_mpoly--Length(VarBinary) None mix_3_poly_polyh_mpt--Data(Vertex) ComponentwiseDelta mix_3_poly_polyh_mpt--Length(Geometries) None mix_3_poly_polyh_mpt--Length(Parts) None mix_3_poly_polyh_mpt--Length(Rings) Rle mix_3_poly_polyh_mpt--Length(VarBinary) None mix_3_polyh_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_3_polyh_mline_mpoly--Length(Geometries) Rle mix_3_polyh_mline_mpoly--Length(Parts) None mix_3_polyh_mline_mpoly--Length(Rings) Rle mix_3_polyh_mline_mpoly--Length(VarBinary) None mix_3_polyh_mpt_mline--Data(Vertex) ComponentwiseDelta mix_3_polyh_mpt_mline--Length(Geometries) None mix_3_polyh_mpt_mline--Length(Parts) None mix_3_polyh_mpt_mline--Length(Rings) None mix_3_polyh_mpt_mline--Length(VarBinary) None mix_3_polyh_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_3_polyh_mpt_mpoly--Length(Geometries) None mix_3_polyh_mpt_mpoly--Length(Parts) None mix_3_polyh_mpt_mpoly--Length(Rings) Rle mix_3_polyh_mpt_mpoly--Length(VarBinary) None mix_3_pt_line_mline--Data(Vertex) ComponentwiseDelta mix_3_pt_line_mline--Length(Geometries) None mix_3_pt_line_mline--Length(Parts) None mix_3_pt_line_mline--Length(VarBinary) None mix_3_pt_line_mpoly--Data(Vertex) ComponentwiseDelta mix_3_pt_line_mpoly--Length(Geometries) None mix_3_pt_line_mpoly--Length(Parts) None mix_3_pt_line_mpoly--Length(Rings) Rle mix_3_pt_line_mpoly--Length(VarBinary) None mix_3_pt_line_mpt--Data(Vertex) ComponentwiseDelta mix_3_pt_line_mpt--Length(Geometries) None mix_3_pt_line_mpt--Length(Parts) None mix_3_pt_line_mpt--Length(VarBinary) None mix_3_pt_line_poly--Data(Vertex) ComponentwiseDelta mix_3_pt_line_poly--Length(Parts) None mix_3_pt_line_poly--Length(Rings) Rle mix_3_pt_line_poly--Length(VarBinary) None mix_3_pt_line_polyh--Data(Vertex) ComponentwiseDelta mix_3_pt_line_polyh--Length(Parts) None mix_3_pt_line_polyh--Length(Rings) Rle mix_3_pt_line_polyh--Length(VarBinary) None mix_3_pt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_3_pt_mline_mpoly--Length(Geometries) Rle mix_3_pt_mline_mpoly--Length(Parts) None mix_3_pt_mline_mpoly--Length(Rings) Rle mix_3_pt_mline_mpoly--Length(VarBinary) None mix_3_pt_mpt_mline--Data(Vertex) ComponentwiseDelta mix_3_pt_mpt_mline--Length(Geometries) None mix_3_pt_mpt_mline--Length(Parts) None mix_3_pt_mpt_mline--Length(VarBinary) None mix_3_pt_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_3_pt_mpt_mpoly--Length(Geometries) None mix_3_pt_mpt_mpoly--Length(Parts) None mix_3_pt_mpt_mpoly--Length(Rings) Rle mix_3_pt_mpt_mpoly--Length(VarBinary) None mix_3_pt_poly_mline--Data(Vertex) ComponentwiseDelta mix_3_pt_poly_mline--Length(Geometries) None mix_3_pt_poly_mline--Length(Parts) None mix_3_pt_poly_mline--Length(Rings) None mix_3_pt_poly_mline--Length(VarBinary) None mix_3_pt_poly_mpoly--Data(Vertex) ComponentwiseDelta mix_3_pt_poly_mpoly--Length(Geometries) None mix_3_pt_poly_mpoly--Length(Parts) None mix_3_pt_poly_mpoly--Length(Rings) Rle mix_3_pt_poly_mpoly--Length(VarBinary) None mix_3_pt_poly_mpt--Data(Vertex) ComponentwiseDelta mix_3_pt_poly_mpt--Length(Geometries) None mix_3_pt_poly_mpt--Length(Parts) None mix_3_pt_poly_mpt--Length(Rings) None mix_3_pt_poly_mpt--Length(VarBinary) None mix_3_pt_poly_polyh--Data(Vertex) ComponentwiseDelta mix_3_pt_poly_polyh--Length(Parts) None mix_3_pt_poly_polyh--Length(Rings) Rle mix_3_pt_poly_polyh--Length(VarBinary) None mix_3_pt_polyh_mline--Data(Vertex) ComponentwiseDelta mix_3_pt_polyh_mline--Length(Geometries) None mix_3_pt_polyh_mline--Length(Parts) None mix_3_pt_polyh_mline--Length(Rings) None mix_3_pt_polyh_mline--Length(VarBinary) None mix_3_pt_polyh_mpoly--Data(Vertex) ComponentwiseDelta mix_3_pt_polyh_mpoly--Length(Geometries) None mix_3_pt_polyh_mpoly--Length(Parts) None mix_3_pt_polyh_mpoly--Length(Rings) Rle mix_3_pt_polyh_mpoly--Length(VarBinary) None mix_3_pt_polyh_mpt--Data(Vertex) ComponentwiseDelta mix_3_pt_polyh_mpt--Length(Geometries) None mix_3_pt_polyh_mpt--Length(Parts) None mix_3_pt_polyh_mpt--Length(Rings) Rle mix_3_pt_polyh_mpt--Length(VarBinary) None mix_4_line_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_line_mpt_mline_mpoly--Length(Geometries) None mix_4_line_mpt_mline_mpoly--Length(Parts) None mix_4_line_mpt_mline_mpoly--Length(Rings) None mix_4_line_mpt_mline_mpoly--Length(VarBinary) None mix_4_line_poly_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_line_poly_mline_mpoly--Length(Geometries) Rle mix_4_line_poly_mline_mpoly--Length(Parts) None mix_4_line_poly_mline_mpoly--Length(Rings) Rle mix_4_line_poly_mline_mpoly--Length(VarBinary) None mix_4_line_poly_mpt_mline--Data(Vertex) ComponentwiseDelta mix_4_line_poly_mpt_mline--Length(Geometries) None mix_4_line_poly_mpt_mline--Length(Parts) None mix_4_line_poly_mpt_mline--Length(Rings) None mix_4_line_poly_mpt_mline--Length(VarBinary) DeltaRle mix_4_line_poly_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_4_line_poly_mpt_mpoly--Length(Geometries) None mix_4_line_poly_mpt_mpoly--Length(Parts) None mix_4_line_poly_mpt_mpoly--Length(Rings) Rle mix_4_line_poly_mpt_mpoly--Length(VarBinary) None mix_4_line_poly_polyh_mline--Data(Vertex) ComponentwiseDelta mix_4_line_poly_polyh_mline--Length(Geometries) None mix_4_line_poly_polyh_mline--Length(Parts) None mix_4_line_poly_polyh_mline--Length(Rings) None mix_4_line_poly_polyh_mline--Length(VarBinary) None mix_4_line_poly_polyh_mpoly--Data(Vertex) ComponentwiseDelta mix_4_line_poly_polyh_mpoly--Length(Geometries) None mix_4_line_poly_polyh_mpoly--Length(Parts) None mix_4_line_poly_polyh_mpoly--Length(Rings) Rle mix_4_line_poly_polyh_mpoly--Length(VarBinary) None mix_4_line_poly_polyh_mpt--Data(Vertex) ComponentwiseDelta mix_4_line_poly_polyh_mpt--Length(Geometries) None mix_4_line_poly_polyh_mpt--Length(Parts) None mix_4_line_poly_polyh_mpt--Length(Rings) Rle mix_4_line_poly_polyh_mpt--Length(VarBinary) None mix_4_line_polyh_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_line_polyh_mline_mpoly--Length(Geometries) Rle mix_4_line_polyh_mline_mpoly--Length(Parts) None mix_4_line_polyh_mline_mpoly--Length(Rings) Rle mix_4_line_polyh_mline_mpoly--Length(VarBinary) None mix_4_line_polyh_mpt_mline--Data(Vertex) ComponentwiseDelta mix_4_line_polyh_mpt_mline--Length(Geometries) None mix_4_line_polyh_mpt_mline--Length(Parts) None mix_4_line_polyh_mpt_mline--Length(Rings) None mix_4_line_polyh_mpt_mline--Length(VarBinary) DeltaRle mix_4_line_polyh_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_4_line_polyh_mpt_mpoly--Length(Geometries) None mix_4_line_polyh_mpt_mpoly--Length(Parts) None mix_4_line_polyh_mpt_mpoly--Length(Rings) Rle mix_4_line_polyh_mpt_mpoly--Length(VarBinary) None mix_4_poly_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_poly_mpt_mline_mpoly--Length(Geometries) None mix_4_poly_mpt_mline_mpoly--Length(Parts) None mix_4_poly_mpt_mline_mpoly--Length(Rings) None mix_4_poly_mpt_mline_mpoly--Length(VarBinary) None mix_4_poly_polyh_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_poly_polyh_mline_mpoly--Length(Geometries) Rle mix_4_poly_polyh_mline_mpoly--Length(Parts) None mix_4_poly_polyh_mline_mpoly--Length(Rings) Rle mix_4_poly_polyh_mline_mpoly--Length(VarBinary) None mix_4_poly_polyh_mpt_mline--Data(Vertex) ComponentwiseDelta mix_4_poly_polyh_mpt_mline--Length(Geometries) None mix_4_poly_polyh_mpt_mline--Length(Parts) None mix_4_poly_polyh_mpt_mline--Length(Rings) None mix_4_poly_polyh_mpt_mline--Length(VarBinary) None mix_4_poly_polyh_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_4_poly_polyh_mpt_mpoly--Length(Geometries) None mix_4_poly_polyh_mpt_mpoly--Length(Parts) None mix_4_poly_polyh_mpt_mpoly--Length(Rings) Rle mix_4_poly_polyh_mpt_mpoly--Length(VarBinary) None mix_4_polyh_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_polyh_mpt_mline_mpoly--Length(Geometries) None mix_4_polyh_mpt_mline_mpoly--Length(Parts) None mix_4_polyh_mpt_mline_mpoly--Length(Rings) Rle mix_4_polyh_mpt_mline_mpoly--Length(VarBinary) None mix_4_pt_line_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_line_mline_mpoly--Length(Geometries) Rle mix_4_pt_line_mline_mpoly--Length(Parts) None mix_4_pt_line_mline_mpoly--Length(Rings) None mix_4_pt_line_mline_mpoly--Length(VarBinary) None mix_4_pt_line_mpt_mline--Data(Vertex) ComponentwiseDelta mix_4_pt_line_mpt_mline--Length(Geometries) None mix_4_pt_line_mpt_mline--Length(Parts) None mix_4_pt_line_mpt_mline--Length(VarBinary) None mix_4_pt_line_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_line_mpt_mpoly--Length(Geometries) None mix_4_pt_line_mpt_mpoly--Length(Parts) None mix_4_pt_line_mpt_mpoly--Length(Rings) Rle mix_4_pt_line_mpt_mpoly--Length(VarBinary) None mix_4_pt_line_poly_mline--Data(Vertex) ComponentwiseDelta mix_4_pt_line_poly_mline--Length(Geometries) None mix_4_pt_line_poly_mline--Length(Parts) None mix_4_pt_line_poly_mline--Length(Rings) None mix_4_pt_line_poly_mline--Length(VarBinary) None mix_4_pt_line_poly_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_line_poly_mpoly--Length(Geometries) None mix_4_pt_line_poly_mpoly--Length(Parts) None mix_4_pt_line_poly_mpoly--Length(Rings) Rle mix_4_pt_line_poly_mpoly--Length(VarBinary) None mix_4_pt_line_poly_mpt--Data(Vertex) ComponentwiseDelta mix_4_pt_line_poly_mpt--Length(Geometries) None mix_4_pt_line_poly_mpt--Length(Parts) None mix_4_pt_line_poly_mpt--Length(Rings) Rle mix_4_pt_line_poly_mpt--Length(VarBinary) None mix_4_pt_line_poly_polyh--Data(Vertex) ComponentwiseDelta mix_4_pt_line_poly_polyh--Length(Parts) None mix_4_pt_line_poly_polyh--Length(Rings) Rle mix_4_pt_line_poly_polyh--Length(VarBinary) None mix_4_pt_line_polyh_mline--Data(Vertex) ComponentwiseDelta mix_4_pt_line_polyh_mline--Length(Geometries) None mix_4_pt_line_polyh_mline--Length(Parts) None mix_4_pt_line_polyh_mline--Length(Rings) None mix_4_pt_line_polyh_mline--Length(VarBinary) None mix_4_pt_line_polyh_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_line_polyh_mpoly--Length(Geometries) None mix_4_pt_line_polyh_mpoly--Length(Parts) None mix_4_pt_line_polyh_mpoly--Length(Rings) Rle mix_4_pt_line_polyh_mpoly--Length(VarBinary) None mix_4_pt_line_polyh_mpt--Data(Vertex) ComponentwiseDelta mix_4_pt_line_polyh_mpt--Length(Geometries) None mix_4_pt_line_polyh_mpt--Length(Parts) None mix_4_pt_line_polyh_mpt--Length(Rings) Rle mix_4_pt_line_polyh_mpt--Length(VarBinary) None mix_4_pt_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_mpt_mline_mpoly--Length(Geometries) None mix_4_pt_mpt_mline_mpoly--Length(Parts) None mix_4_pt_mpt_mline_mpoly--Length(Rings) Rle mix_4_pt_mpt_mline_mpoly--Length(VarBinary) None mix_4_pt_poly_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_poly_mline_mpoly--Length(Geometries) Rle mix_4_pt_poly_mline_mpoly--Length(Parts) None mix_4_pt_poly_mline_mpoly--Length(Rings) None mix_4_pt_poly_mline_mpoly--Length(VarBinary) None mix_4_pt_poly_mpt_mline--Data(Vertex) ComponentwiseDelta mix_4_pt_poly_mpt_mline--Length(Geometries) None mix_4_pt_poly_mpt_mline--Length(Parts) None mix_4_pt_poly_mpt_mline--Length(Rings) None mix_4_pt_poly_mpt_mline--Length(VarBinary) None mix_4_pt_poly_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_poly_mpt_mpoly--Length(Geometries) None mix_4_pt_poly_mpt_mpoly--Length(Parts) None mix_4_pt_poly_mpt_mpoly--Length(Rings) Rle mix_4_pt_poly_mpt_mpoly--Length(VarBinary) None mix_4_pt_poly_polyh_mline--Data(Vertex) ComponentwiseDelta mix_4_pt_poly_polyh_mline--Length(Geometries) None mix_4_pt_poly_polyh_mline--Length(Parts) None mix_4_pt_poly_polyh_mline--Length(Rings) None mix_4_pt_poly_polyh_mline--Length(VarBinary) None mix_4_pt_poly_polyh_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_poly_polyh_mpoly--Length(Geometries) None mix_4_pt_poly_polyh_mpoly--Length(Parts) None mix_4_pt_poly_polyh_mpoly--Length(Rings) Rle mix_4_pt_poly_polyh_mpoly--Length(VarBinary) None mix_4_pt_poly_polyh_mpt--Data(Vertex) ComponentwiseDelta mix_4_pt_poly_polyh_mpt--Length(Geometries) None mix_4_pt_poly_polyh_mpt--Length(Parts) None mix_4_pt_poly_polyh_mpt--Length(Rings) Rle mix_4_pt_poly_polyh_mpt--Length(VarBinary) None mix_4_pt_polyh_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_polyh_mline_mpoly--Length(Geometries) Rle mix_4_pt_polyh_mline_mpoly--Length(Parts) None mix_4_pt_polyh_mline_mpoly--Length(Rings) Rle mix_4_pt_polyh_mline_mpoly--Length(VarBinary) None mix_4_pt_polyh_mpt_mline--Data(Vertex) ComponentwiseDelta mix_4_pt_polyh_mpt_mline--Length(Geometries) None mix_4_pt_polyh_mpt_mline--Length(Parts) None mix_4_pt_polyh_mpt_mline--Length(Rings) None mix_4_pt_polyh_mpt_mline--Length(VarBinary) None mix_4_pt_polyh_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_polyh_mpt_mpoly--Length(Geometries) None mix_4_pt_polyh_mpt_mpoly--Length(Parts) None mix_4_pt_polyh_mpt_mpoly--Length(Rings) Rle mix_4_pt_polyh_mpt_mpoly--Length(VarBinary) None mix_5_line_poly_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_line_poly_mpt_mline_mpoly--Length(Geometries) None mix_5_line_poly_mpt_mline_mpoly--Length(Parts) None mix_5_line_poly_mpt_mline_mpoly--Length(Rings) Rle mix_5_line_poly_mpt_mline_mpoly--Length(VarBinary) DeltaRle mix_5_line_poly_polyh_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_line_poly_polyh_mline_mpoly--Length(Geometries) Rle mix_5_line_poly_polyh_mline_mpoly--Length(Parts) None mix_5_line_poly_polyh_mline_mpoly--Length(Rings) Rle mix_5_line_poly_polyh_mline_mpoly--Length(VarBinary) None mix_5_line_poly_polyh_mpt_mline--Data(Vertex) ComponentwiseDelta mix_5_line_poly_polyh_mpt_mline--Length(Geometries) None mix_5_line_poly_polyh_mpt_mline--Length(Parts) None mix_5_line_poly_polyh_mpt_mline--Length(Rings) None mix_5_line_poly_polyh_mpt_mline--Length(VarBinary) None mix_5_line_poly_polyh_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_5_line_poly_polyh_mpt_mpoly--Length(Geometries) None mix_5_line_poly_polyh_mpt_mpoly--Length(Parts) None mix_5_line_poly_polyh_mpt_mpoly--Length(Rings) Rle mix_5_line_poly_polyh_mpt_mpoly--Length(VarBinary) None mix_5_line_polyh_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_line_polyh_mpt_mline_mpoly--Length(Geometries) None mix_5_line_polyh_mpt_mline_mpoly--Length(Parts) None mix_5_line_polyh_mpt_mline_mpoly--Length(Rings) Rle mix_5_line_polyh_mpt_mline_mpoly--Length(VarBinary) DeltaRle mix_5_poly_polyh_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_poly_polyh_mpt_mline_mpoly--Length(Geometries) None mix_5_poly_polyh_mpt_mline_mpoly--Length(Parts) None mix_5_poly_polyh_mpt_mline_mpoly--Length(Rings) Rle mix_5_poly_polyh_mpt_mline_mpoly--Length(VarBinary) None mix_5_pt_line_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_line_mpt_mline_mpoly--Length(Geometries) None mix_5_pt_line_mpt_mline_mpoly--Length(Parts) None mix_5_pt_line_mpt_mline_mpoly--Length(Rings) None mix_5_pt_line_mpt_mline_mpoly--Length(VarBinary) None mix_5_pt_line_poly_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_line_poly_mline_mpoly--Length(Geometries) Rle mix_5_pt_line_poly_mline_mpoly--Length(Parts) None mix_5_pt_line_poly_mline_mpoly--Length(Rings) Rle mix_5_pt_line_poly_mline_mpoly--Length(VarBinary) None mix_5_pt_line_poly_mpt_mline--Data(Vertex) ComponentwiseDelta mix_5_pt_line_poly_mpt_mline--Length(Geometries) None mix_5_pt_line_poly_mpt_mline--Length(Parts) None mix_5_pt_line_poly_mpt_mline--Length(Rings) None mix_5_pt_line_poly_mpt_mline--Length(VarBinary) DeltaRle mix_5_pt_line_poly_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_line_poly_mpt_mpoly--Length(Geometries) None mix_5_pt_line_poly_mpt_mpoly--Length(Parts) None mix_5_pt_line_poly_mpt_mpoly--Length(Rings) Rle mix_5_pt_line_poly_mpt_mpoly--Length(VarBinary) None mix_5_pt_line_poly_polyh_mline--Data(Vertex) ComponentwiseDelta mix_5_pt_line_poly_polyh_mline--Length(Geometries) None mix_5_pt_line_poly_polyh_mline--Length(Parts) None mix_5_pt_line_poly_polyh_mline--Length(Rings) None mix_5_pt_line_poly_polyh_mline--Length(VarBinary) None mix_5_pt_line_poly_polyh_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_line_poly_polyh_mpoly--Length(Geometries) None mix_5_pt_line_poly_polyh_mpoly--Length(Parts) None mix_5_pt_line_poly_polyh_mpoly--Length(Rings) Rle mix_5_pt_line_poly_polyh_mpoly--Length(VarBinary) None mix_5_pt_line_poly_polyh_mpt--Data(Vertex) ComponentwiseDelta mix_5_pt_line_poly_polyh_mpt--Length(Geometries) None mix_5_pt_line_poly_polyh_mpt--Length(Parts) None mix_5_pt_line_poly_polyh_mpt--Length(Rings) Rle mix_5_pt_line_poly_polyh_mpt--Length(VarBinary) None mix_5_pt_line_polyh_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_line_polyh_mline_mpoly--Length(Geometries) Rle mix_5_pt_line_polyh_mline_mpoly--Length(Parts) None mix_5_pt_line_polyh_mline_mpoly--Length(Rings) Rle mix_5_pt_line_polyh_mline_mpoly--Length(VarBinary) None mix_5_pt_line_polyh_mpt_mline--Data(Vertex) ComponentwiseDelta mix_5_pt_line_polyh_mpt_mline--Length(Geometries) None mix_5_pt_line_polyh_mpt_mline--Length(Parts) None mix_5_pt_line_polyh_mpt_mline--Length(Rings) None mix_5_pt_line_polyh_mpt_mline--Length(VarBinary) DeltaRle mix_5_pt_line_polyh_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_line_polyh_mpt_mpoly--Length(Geometries) None mix_5_pt_line_polyh_mpt_mpoly--Length(Parts) None mix_5_pt_line_polyh_mpt_mpoly--Length(Rings) Rle mix_5_pt_line_polyh_mpt_mpoly--Length(VarBinary) None mix_5_pt_poly_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_poly_mpt_mline_mpoly--Length(Geometries) None mix_5_pt_poly_mpt_mline_mpoly--Length(Parts) None mix_5_pt_poly_mpt_mline_mpoly--Length(Rings) None mix_5_pt_poly_mpt_mline_mpoly--Length(VarBinary) None mix_5_pt_poly_polyh_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_poly_polyh_mline_mpoly--Length(Geometries) Rle mix_5_pt_poly_polyh_mline_mpoly--Length(Parts) None mix_5_pt_poly_polyh_mline_mpoly--Length(Rings) Rle mix_5_pt_poly_polyh_mline_mpoly--Length(VarBinary) None mix_5_pt_poly_polyh_mpt_mline--Data(Vertex) ComponentwiseDelta mix_5_pt_poly_polyh_mpt_mline--Length(Geometries) None mix_5_pt_poly_polyh_mpt_mline--Length(Parts) None mix_5_pt_poly_polyh_mpt_mline--Length(Rings) None mix_5_pt_poly_polyh_mpt_mline--Length(VarBinary) None mix_5_pt_poly_polyh_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_poly_polyh_mpt_mpoly--Length(Geometries) None mix_5_pt_poly_polyh_mpt_mpoly--Length(Parts) None mix_5_pt_poly_polyh_mpt_mpoly--Length(Rings) Rle mix_5_pt_poly_polyh_mpt_mpoly--Length(VarBinary) None mix_5_pt_polyh_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_polyh_mpt_mline_mpoly--Length(Geometries) None mix_5_pt_polyh_mpt_mline_mpoly--Length(Parts) None mix_5_pt_polyh_mpt_mline_mpoly--Length(Rings) Rle mix_5_pt_polyh_mpt_mline_mpoly--Length(VarBinary) None mix_6_line_poly_polyh_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_6_line_poly_polyh_mpt_mline_mpoly--Length(Geometries) None mix_6_line_poly_polyh_mpt_mline_mpoly--Length(Parts) None mix_6_line_poly_polyh_mpt_mline_mpoly--Length(Rings) Rle mix_6_line_poly_polyh_mpt_mline_mpoly--Length(VarBinary) None mix_6_pt_line_poly_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_6_pt_line_poly_mpt_mline_mpoly--Length(Geometries) None mix_6_pt_line_poly_mpt_mline_mpoly--Length(Parts) None mix_6_pt_line_poly_mpt_mline_mpoly--Length(Rings) Rle mix_6_pt_line_poly_mpt_mline_mpoly--Length(VarBinary) DeltaRle mix_6_pt_line_poly_polyh_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_6_pt_line_poly_polyh_mline_mpoly--Length(Geometries) Rle mix_6_pt_line_poly_polyh_mline_mpoly--Length(Parts) None mix_6_pt_line_poly_polyh_mline_mpoly--Length(Rings) Rle mix_6_pt_line_poly_polyh_mline_mpoly--Length(VarBinary) None mix_6_pt_line_poly_polyh_mpt_mline--Data(Vertex) ComponentwiseDelta mix_6_pt_line_poly_polyh_mpt_mline--Length(Geometries) None mix_6_pt_line_poly_polyh_mpt_mline--Length(Parts) None mix_6_pt_line_poly_polyh_mpt_mline--Length(Rings) None mix_6_pt_line_poly_polyh_mpt_mline--Length(VarBinary) None mix_6_pt_line_poly_polyh_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_6_pt_line_poly_polyh_mpt_mpoly--Length(Geometries) None mix_6_pt_line_poly_polyh_mpt_mpoly--Length(Parts) None mix_6_pt_line_poly_polyh_mpt_mpoly--Length(Rings) Rle mix_6_pt_line_poly_polyh_mpt_mpoly--Length(VarBinary) None mix_6_pt_line_polyh_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_6_pt_line_polyh_mpt_mline_mpoly--Length(Geometries) None mix_6_pt_line_polyh_mpt_mline_mpoly--Length(Parts) None mix_6_pt_line_polyh_mpt_mline_mpoly--Length(Rings) Rle mix_6_pt_line_polyh_mpt_mline_mpoly--Length(VarBinary) DeltaRle mix_6_pt_poly_polyh_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_6_pt_poly_polyh_mpt_mline_mpoly--Length(Geometries) None mix_6_pt_poly_polyh_mpt_mline_mpoly--Length(Parts) None mix_6_pt_poly_polyh_mpt_mline_mpoly--Length(Rings) Rle mix_6_pt_poly_polyh_mpt_mline_mpoly--Length(VarBinary) None mix_7_pt_line_poly_polyh_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_7_pt_line_poly_polyh_mpt_mline_mpoly--Length(Geometries) None mix_7_pt_line_poly_polyh_mpt_mline_mpoly--Length(Parts) None mix_7_pt_line_poly_polyh_mpt_mline_mpoly--Length(Rings) Rle mix_7_pt_line_poly_polyh_mpt_mline_mpoly--Length(VarBinary) None mix_dup_line--Data(Vertex) ComponentwiseDelta mix_dup_line--Length(Parts) Rle mix_dup_line--Length(VarBinary) Rle mix_dup_mline--Data(Vertex) ComponentwiseDelta mix_dup_mline--Length(Geometries) Rle mix_dup_mline--Length(Parts) None mix_dup_mline--Length(VarBinary) Rle mix_dup_mpoly--Data(Vertex) ComponentwiseDelta mix_dup_mpoly--Length(Geometries) Rle mix_dup_mpoly--Length(Parts) None mix_dup_mpoly--Length(Rings) Rle mix_dup_mpoly--Length(VarBinary) Rle mix_dup_mpt--Data(Vertex) ComponentwiseDelta mix_dup_mpt--Length(Geometries) Rle mix_dup_mpt--Length(VarBinary) Rle mix_dup_poly--Data(Vertex) ComponentwiseDelta mix_dup_poly--Length(Parts) Rle mix_dup_poly--Length(Rings) Rle mix_dup_poly--Length(VarBinary) Rle mix_dup_polyh--Data(Vertex) ComponentwiseDelta mix_dup_polyh--Length(Parts) Rle mix_dup_polyh--Length(Rings) Rle mix_dup_polyh--Length(VarBinary) Rle mix_dup_pt--Data(Vertex) ComponentwiseDelta mix_dup_pt--Length(VarBinary) Rle mix_line_mline_line--Data(Vertex) ComponentwiseDelta mix_line_mline_line--Length(Geometries) None mix_line_mline_line--Length(Parts) None mix_line_mline_line--Length(VarBinary) None mix_line_mpoly_line--Data(Vertex) ComponentwiseDelta mix_line_mpoly_line--Length(Geometries) None mix_line_mpoly_line--Length(Parts) None mix_line_mpoly_line--Length(Rings) Rle mix_line_mpoly_line--Length(VarBinary) None mix_line_mpt_line--Data(Vertex) ComponentwiseDelta mix_line_mpt_line--Length(Geometries) None mix_line_mpt_line--Length(Parts) Rle mix_line_mpt_line--Length(VarBinary) None mix_line_poly_line--Data(Vertex) ComponentwiseDelta mix_line_poly_line--Length(Parts) None mix_line_poly_line--Length(Rings) Rle mix_line_poly_line--Length(VarBinary) None mix_line_polyh_line--Data(Vertex) ComponentwiseDelta mix_line_polyh_line--Length(Parts) None mix_line_polyh_line--Length(Rings) Rle mix_line_polyh_line--Length(VarBinary) None mix_line_pt_line--Data(Vertex) ComponentwiseDelta mix_line_pt_line--Length(Parts) Rle mix_line_pt_line--Length(VarBinary) None mix_mline_line_mline--Data(Vertex) ComponentwiseDelta mix_mline_line_mline--Length(Geometries) Rle mix_mline_line_mline--Length(Parts) None mix_mline_line_mline--Length(VarBinary) None mix_mline_mpoly_mline--Data(Vertex) ComponentwiseDelta mix_mline_mpoly_mline--Length(Geometries) Rle mix_mline_mpoly_mline--Length(Parts) None mix_mline_mpoly_mline--Length(Rings) None mix_mline_mpoly_mline--Length(VarBinary) None mix_mline_mpt_mline--Data(Vertex) ComponentwiseDelta mix_mline_mpt_mline--Length(Geometries) None mix_mline_mpt_mline--Length(Parts) None mix_mline_mpt_mline--Length(VarBinary) None mix_mline_poly_mline--Data(Vertex) ComponentwiseDelta mix_mline_poly_mline--Length(Geometries) Rle mix_mline_poly_mline--Length(Parts) None mix_mline_poly_mline--Length(Rings) None mix_mline_poly_mline--Length(VarBinary) None mix_mline_polyh_mline--Data(Vertex) ComponentwiseDelta mix_mline_polyh_mline--Length(Geometries) Rle mix_mline_polyh_mline--Length(Parts) None mix_mline_polyh_mline--Length(Rings) None mix_mline_polyh_mline--Length(VarBinary) None mix_mline_pt_mline--Data(Vertex) ComponentwiseDelta mix_mline_pt_mline--Length(Geometries) Rle mix_mline_pt_mline--Length(Parts) None mix_mline_pt_mline--Length(VarBinary) None mix_mpoly_line_mpoly--Data(Vertex) ComponentwiseDelta mix_mpoly_line_mpoly--Length(Geometries) Rle mix_mpoly_line_mpoly--Length(Parts) None mix_mpoly_line_mpoly--Length(Rings) Rle mix_mpoly_line_mpoly--Length(VarBinary) None mix_mpoly_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_mpoly_mline_mpoly--Length(Geometries) Rle mix_mpoly_mline_mpoly--Length(Parts) None mix_mpoly_mline_mpoly--Length(Rings) Rle mix_mpoly_mline_mpoly--Length(VarBinary) None mix_mpoly_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_mpoly_mpt_mpoly--Length(Geometries) None mix_mpoly_mpt_mpoly--Length(Parts) None mix_mpoly_mpt_mpoly--Length(Rings) Rle mix_mpoly_mpt_mpoly--Length(VarBinary) None mix_mpoly_poly_mpoly--Data(Vertex) ComponentwiseDelta mix_mpoly_poly_mpoly--Length(Geometries) Rle mix_mpoly_poly_mpoly--Length(Parts) None mix_mpoly_poly_mpoly--Length(Rings) Rle mix_mpoly_poly_mpoly--Length(VarBinary) None mix_mpoly_polyh_mpoly--Data(Vertex) ComponentwiseDelta mix_mpoly_polyh_mpoly--Length(Geometries) Rle mix_mpoly_polyh_mpoly--Length(Parts) None mix_mpoly_polyh_mpoly--Length(Rings) Rle mix_mpoly_polyh_mpoly--Length(VarBinary) None mix_mpoly_pt_mpoly--Data(Vertex) ComponentwiseDelta mix_mpoly_pt_mpoly--Length(Geometries) Rle mix_mpoly_pt_mpoly--Length(Parts) None mix_mpoly_pt_mpoly--Length(Rings) Rle mix_mpoly_pt_mpoly--Length(VarBinary) None mix_mpt_line_mpt--Data(Vertex) ComponentwiseDelta mix_mpt_line_mpt--Length(Geometries) Rle mix_mpt_line_mpt--Length(Parts) None mix_mpt_line_mpt--Length(VarBinary) None mix_mpt_mline_mpt--Data(Vertex) ComponentwiseDelta mix_mpt_mline_mpt--Length(Geometries) None mix_mpt_mline_mpt--Length(Parts) None mix_mpt_mline_mpt--Length(VarBinary) None mix_mpt_mpoly_mpt--Data(Vertex) ComponentwiseDelta mix_mpt_mpoly_mpt--Length(Geometries) None mix_mpt_mpoly_mpt--Length(Parts) None mix_mpt_mpoly_mpt--Length(Rings) Rle mix_mpt_mpoly_mpt--Length(VarBinary) None mix_mpt_poly_mpt--Data(Vertex) ComponentwiseDelta mix_mpt_poly_mpt--Length(Geometries) Rle mix_mpt_poly_mpt--Length(Parts) None mix_mpt_poly_mpt--Length(Rings) None mix_mpt_poly_mpt--Length(VarBinary) None mix_mpt_polyh_mpt--Data(Vertex) ComponentwiseDelta mix_mpt_polyh_mpt--Length(Geometries) Rle mix_mpt_polyh_mpt--Length(Parts) None mix_mpt_polyh_mpt--Length(Rings) Rle mix_mpt_polyh_mpt--Length(VarBinary) None mix_mpt_pt_mpt--Data(Vertex) ComponentwiseDelta mix_mpt_pt_mpt--Length(Geometries) Rle mix_mpt_pt_mpt--Length(VarBinary) None mix_poly_line_poly--Data(Vertex) ComponentwiseDelta mix_poly_line_poly--Length(Parts) Rle mix_poly_line_poly--Length(Rings) Rle mix_poly_line_poly--Length(VarBinary) None mix_poly_mline_poly--Data(Vertex) ComponentwiseDelta mix_poly_mline_poly--Length(Geometries) None mix_poly_mline_poly--Length(Parts) Rle mix_poly_mline_poly--Length(Rings) None mix_poly_mline_poly--Length(VarBinary) None mix_poly_mpoly_poly--Data(Vertex) ComponentwiseDelta mix_poly_mpoly_poly--Length(Geometries) None mix_poly_mpoly_poly--Length(Parts) None mix_poly_mpoly_poly--Length(Rings) Rle mix_poly_mpoly_poly--Length(VarBinary) None mix_poly_mpt_poly--Data(Vertex) ComponentwiseDelta mix_poly_mpt_poly--Length(Geometries) None mix_poly_mpt_poly--Length(Parts) Rle mix_poly_mpt_poly--Length(Rings) Rle mix_poly_mpt_poly--Length(VarBinary) None mix_poly_polyh_poly--Data(Vertex) ComponentwiseDelta mix_poly_polyh_poly--Length(Parts) None mix_poly_polyh_poly--Length(Rings) Rle mix_poly_polyh_poly--Length(VarBinary) Rle mix_poly_pt_poly--Data(Vertex) ComponentwiseDelta mix_poly_pt_poly--Length(Parts) Rle mix_poly_pt_poly--Length(Rings) Rle mix_poly_pt_poly--Length(VarBinary) None mix_polyh_line_polyh--Data(Vertex) ComponentwiseDelta mix_polyh_line_polyh--Length(Parts) Rle mix_polyh_line_polyh--Length(Rings) Rle mix_polyh_line_polyh--Length(VarBinary) None mix_polyh_mline_polyh--Data(Vertex) ComponentwiseDelta mix_polyh_mline_polyh--Length(Geometries) None mix_polyh_mline_polyh--Length(Parts) Rle mix_polyh_mline_polyh--Length(Rings) None mix_polyh_mline_polyh--Length(VarBinary) None mix_polyh_mpoly_polyh--Data(Vertex) ComponentwiseDelta mix_polyh_mpoly_polyh--Length(Geometries) None mix_polyh_mpoly_polyh--Length(Parts) None mix_polyh_mpoly_polyh--Length(Rings) Rle mix_polyh_mpoly_polyh--Length(VarBinary) None mix_polyh_mpt_polyh--Data(Vertex) ComponentwiseDelta mix_polyh_mpt_polyh--Length(Geometries) None mix_polyh_mpt_polyh--Length(Parts) Rle mix_polyh_mpt_polyh--Length(Rings) Rle mix_polyh_mpt_polyh--Length(VarBinary) None mix_polyh_poly_polyh--Data(Vertex) ComponentwiseDelta mix_polyh_poly_polyh--Length(Parts) None mix_polyh_poly_polyh--Length(Rings) Rle mix_polyh_poly_polyh--Length(VarBinary) Rle mix_polyh_pt_polyh--Data(Vertex) ComponentwiseDelta mix_polyh_pt_polyh--Length(Parts) Rle mix_polyh_pt_polyh--Length(Rings) Rle mix_polyh_pt_polyh--Length(VarBinary) None mix_pt_line_pt--Data(Vertex) ComponentwiseDelta mix_pt_line_pt--Length(Parts) None mix_pt_line_pt--Length(VarBinary) None mix_pt_mline_pt--Data(Vertex) ComponentwiseDelta mix_pt_mline_pt--Length(Geometries) None mix_pt_mline_pt--Length(Parts) None mix_pt_mline_pt--Length(VarBinary) None mix_pt_mpoly_pt--Data(Vertex) ComponentwiseDelta mix_pt_mpoly_pt--Length(Geometries) None mix_pt_mpoly_pt--Length(Parts) None mix_pt_mpoly_pt--Length(Rings) Rle mix_pt_mpoly_pt--Length(VarBinary) None mix_pt_mpt_pt--Data(Vertex) ComponentwiseDelta mix_pt_mpt_pt--Length(Geometries) None mix_pt_mpt_pt--Length(VarBinary) None mix_pt_poly_pt--Data(Vertex) ComponentwiseDelta mix_pt_poly_pt--Length(Parts) None mix_pt_poly_pt--Length(Rings) None mix_pt_poly_pt--Length(VarBinary) None mix_pt_polyh_pt--Data(Vertex) ComponentwiseDelta mix_pt_polyh_pt--Length(Parts) None mix_pt_polyh_pt--Length(Rings) Rle mix_pt_polyh_pt--Length(VarBinary) None ================================================ FILE: test/synthetic/rust.txt ================================================ mix_2_line_mline--Data(Vertex) ComponentwiseDelta mix_2_line_mline--Length(Geometries) None mix_2_line_mline--Length(Parts) None mix_2_line_mline--Length(VarBinary) None mix_2_line_mpoly--Data(Vertex) ComponentwiseDelta mix_2_line_mpoly--Length(Geometries) None mix_2_line_mpoly--Length(Parts) None mix_2_line_mpoly--Length(Rings) None mix_2_line_mpoly--Length(VarBinary) None mix_2_line_mpt--Data(Vertex) ComponentwiseDelta mix_2_line_mpt--Length(Geometries) None mix_2_line_mpt--Length(Parts) None mix_2_line_mpt--Length(VarBinary) None mix_2_line_poly--Data(Vertex) ComponentwiseDelta mix_2_line_poly--Length(Parts) None mix_2_line_poly--Length(Rings) None mix_2_line_poly--Length(VarBinary) None mix_2_line_polyh--Data(Vertex) ComponentwiseDelta mix_2_line_polyh--Length(Parts) None mix_2_line_polyh--Length(Rings) None mix_2_line_polyh--Length(VarBinary) None mix_2_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_2_mline_mpoly--Length(Geometries) None mix_2_mline_mpoly--Length(Parts) None mix_2_mline_mpoly--Length(Rings) None mix_2_mline_mpoly--Length(VarBinary) None mix_2_mpt_mline--Data(Vertex) ComponentwiseDelta mix_2_mpt_mline--Length(Geometries) None mix_2_mpt_mline--Length(Parts) None mix_2_mpt_mline--Length(VarBinary) None mix_2_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_2_mpt_mpoly--Length(Geometries) None mix_2_mpt_mpoly--Length(Parts) None mix_2_mpt_mpoly--Length(Rings) None mix_2_mpt_mpoly--Length(VarBinary) None mix_2_poly_mline--Data(Vertex) ComponentwiseDelta mix_2_poly_mline--Length(Geometries) None mix_2_poly_mline--Length(Parts) None mix_2_poly_mline--Length(Rings) None mix_2_poly_mline--Length(VarBinary) None mix_2_poly_mpoly--Data(Vertex) ComponentwiseDelta mix_2_poly_mpoly--Length(Geometries) None mix_2_poly_mpoly--Length(Parts) None mix_2_poly_mpoly--Length(Rings) None mix_2_poly_mpoly--Length(VarBinary) None mix_2_poly_mpt--Data(Vertex) ComponentwiseDelta mix_2_poly_mpt--Length(Geometries) None mix_2_poly_mpt--Length(Parts) None mix_2_poly_mpt--Length(Rings) None mix_2_poly_mpt--Length(VarBinary) None mix_2_poly_polyh--Data(Vertex) ComponentwiseDelta mix_2_poly_polyh--Length(Parts) None mix_2_poly_polyh--Length(Rings) None mix_2_poly_polyh--Length(VarBinary) None mix_2_polyh_mline--Data(Vertex) ComponentwiseDelta mix_2_polyh_mline--Length(Geometries) None mix_2_polyh_mline--Length(Parts) None mix_2_polyh_mline--Length(Rings) None mix_2_polyh_mline--Length(VarBinary) None mix_2_polyh_mpoly--Data(Vertex) ComponentwiseDelta mix_2_polyh_mpoly--Length(Geometries) None mix_2_polyh_mpoly--Length(Parts) None mix_2_polyh_mpoly--Length(Rings) None mix_2_polyh_mpoly--Length(VarBinary) None mix_2_polyh_mpt--Data(Vertex) ComponentwiseDelta mix_2_polyh_mpt--Length(Geometries) None mix_2_polyh_mpt--Length(Parts) None mix_2_polyh_mpt--Length(Rings) None mix_2_polyh_mpt--Length(VarBinary) None mix_2_pt_line--Data(Vertex) ComponentwiseDelta mix_2_pt_line--Length(Parts) None mix_2_pt_line--Length(VarBinary) None mix_2_pt_mline--Data(Vertex) ComponentwiseDelta mix_2_pt_mline--Length(Geometries) None mix_2_pt_mline--Length(Parts) None mix_2_pt_mline--Length(VarBinary) None mix_2_pt_mpoly--Data(Vertex) ComponentwiseDelta mix_2_pt_mpoly--Length(Geometries) None mix_2_pt_mpoly--Length(Parts) None mix_2_pt_mpoly--Length(Rings) None mix_2_pt_mpoly--Length(VarBinary) None mix_2_pt_mpt--Data(Vertex) ComponentwiseDelta mix_2_pt_mpt--Length(Geometries) None mix_2_pt_mpt--Length(VarBinary) None mix_2_pt_poly--Data(Vertex) ComponentwiseDelta mix_2_pt_poly--Length(Parts) None mix_2_pt_poly--Length(Rings) None mix_2_pt_poly--Length(VarBinary) None mix_2_pt_polyh--Data(Vertex) ComponentwiseDelta mix_2_pt_polyh--Length(Parts) None mix_2_pt_polyh--Length(Rings) None mix_2_pt_polyh--Length(VarBinary) None mix_3_line_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_3_line_mline_mpoly--Length(Geometries) None mix_3_line_mline_mpoly--Length(Parts) None mix_3_line_mline_mpoly--Length(Rings) None mix_3_line_mline_mpoly--Length(VarBinary) None mix_3_line_mpt_mline--Data(Vertex) ComponentwiseDelta mix_3_line_mpt_mline--Length(Geometries) None mix_3_line_mpt_mline--Length(Parts) None mix_3_line_mpt_mline--Length(VarBinary) None mix_3_line_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_3_line_mpt_mpoly--Length(Geometries) None mix_3_line_mpt_mpoly--Length(Parts) None mix_3_line_mpt_mpoly--Length(Rings) None mix_3_line_mpt_mpoly--Length(VarBinary) None mix_3_line_poly_mline--Data(Vertex) ComponentwiseDelta mix_3_line_poly_mline--Length(Geometries) None mix_3_line_poly_mline--Length(Parts) None mix_3_line_poly_mline--Length(Rings) None mix_3_line_poly_mline--Length(VarBinary) None mix_3_line_poly_mpoly--Data(Vertex) ComponentwiseDelta mix_3_line_poly_mpoly--Length(Geometries) None mix_3_line_poly_mpoly--Length(Parts) None mix_3_line_poly_mpoly--Length(Rings) None mix_3_line_poly_mpoly--Length(VarBinary) None mix_3_line_poly_mpt--Data(Vertex) ComponentwiseDelta mix_3_line_poly_mpt--Length(Geometries) None mix_3_line_poly_mpt--Length(Parts) None mix_3_line_poly_mpt--Length(Rings) None mix_3_line_poly_mpt--Length(VarBinary) None mix_3_line_poly_polyh--Data(Vertex) ComponentwiseDelta mix_3_line_poly_polyh--Length(Parts) None mix_3_line_poly_polyh--Length(Rings) None mix_3_line_poly_polyh--Length(VarBinary) None mix_3_line_polyh_mline--Data(Vertex) ComponentwiseDelta mix_3_line_polyh_mline--Length(Geometries) None mix_3_line_polyh_mline--Length(Parts) None mix_3_line_polyh_mline--Length(Rings) None mix_3_line_polyh_mline--Length(VarBinary) None mix_3_line_polyh_mpoly--Data(Vertex) ComponentwiseDelta mix_3_line_polyh_mpoly--Length(Geometries) None mix_3_line_polyh_mpoly--Length(Parts) None mix_3_line_polyh_mpoly--Length(Rings) None mix_3_line_polyh_mpoly--Length(VarBinary) None mix_3_line_polyh_mpt--Data(Vertex) ComponentwiseDelta mix_3_line_polyh_mpt--Length(Geometries) None mix_3_line_polyh_mpt--Length(Parts) None mix_3_line_polyh_mpt--Length(Rings) None mix_3_line_polyh_mpt--Length(VarBinary) None mix_3_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_3_mpt_mline_mpoly--Length(Geometries) None mix_3_mpt_mline_mpoly--Length(Parts) None mix_3_mpt_mline_mpoly--Length(Rings) None mix_3_mpt_mline_mpoly--Length(VarBinary) None mix_3_poly_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_3_poly_mline_mpoly--Length(Geometries) None mix_3_poly_mline_mpoly--Length(Parts) None mix_3_poly_mline_mpoly--Length(Rings) None mix_3_poly_mline_mpoly--Length(VarBinary) None mix_3_poly_mpt_mline--Data(Vertex) ComponentwiseDelta mix_3_poly_mpt_mline--Length(Geometries) None mix_3_poly_mpt_mline--Length(Parts) None mix_3_poly_mpt_mline--Length(Rings) None mix_3_poly_mpt_mline--Length(VarBinary) None mix_3_poly_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_3_poly_mpt_mpoly--Length(Geometries) None mix_3_poly_mpt_mpoly--Length(Parts) None mix_3_poly_mpt_mpoly--Length(Rings) None mix_3_poly_mpt_mpoly--Length(VarBinary) None mix_3_poly_polyh_mline--Data(Vertex) ComponentwiseDelta mix_3_poly_polyh_mline--Length(Geometries) None mix_3_poly_polyh_mline--Length(Parts) None mix_3_poly_polyh_mline--Length(Rings) None mix_3_poly_polyh_mline--Length(VarBinary) None mix_3_poly_polyh_mpoly--Data(Vertex) ComponentwiseDelta mix_3_poly_polyh_mpoly--Length(Geometries) None mix_3_poly_polyh_mpoly--Length(Parts) None mix_3_poly_polyh_mpoly--Length(Rings) None mix_3_poly_polyh_mpoly--Length(VarBinary) None mix_3_poly_polyh_mpt--Data(Vertex) ComponentwiseDelta mix_3_poly_polyh_mpt--Length(Geometries) None mix_3_poly_polyh_mpt--Length(Parts) None mix_3_poly_polyh_mpt--Length(Rings) None mix_3_poly_polyh_mpt--Length(VarBinary) None mix_3_polyh_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_3_polyh_mline_mpoly--Length(Geometries) None mix_3_polyh_mline_mpoly--Length(Parts) None mix_3_polyh_mline_mpoly--Length(Rings) None mix_3_polyh_mline_mpoly--Length(VarBinary) None mix_3_polyh_mpt_mline--Data(Vertex) ComponentwiseDelta mix_3_polyh_mpt_mline--Length(Geometries) None mix_3_polyh_mpt_mline--Length(Parts) None mix_3_polyh_mpt_mline--Length(Rings) None mix_3_polyh_mpt_mline--Length(VarBinary) None mix_3_polyh_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_3_polyh_mpt_mpoly--Length(Geometries) None mix_3_polyh_mpt_mpoly--Length(Parts) None mix_3_polyh_mpt_mpoly--Length(Rings) None mix_3_polyh_mpt_mpoly--Length(VarBinary) None mix_3_pt_line_mline--Data(Vertex) ComponentwiseDelta mix_3_pt_line_mline--Length(Geometries) None mix_3_pt_line_mline--Length(Parts) None mix_3_pt_line_mline--Length(VarBinary) None mix_3_pt_line_mpoly--Data(Vertex) ComponentwiseDelta mix_3_pt_line_mpoly--Length(Geometries) None mix_3_pt_line_mpoly--Length(Parts) None mix_3_pt_line_mpoly--Length(Rings) None mix_3_pt_line_mpoly--Length(VarBinary) None mix_3_pt_line_mpt--Data(Vertex) ComponentwiseDelta mix_3_pt_line_mpt--Length(Geometries) None mix_3_pt_line_mpt--Length(Parts) None mix_3_pt_line_mpt--Length(VarBinary) None mix_3_pt_line_poly--Data(Vertex) ComponentwiseDelta mix_3_pt_line_poly--Length(Parts) None mix_3_pt_line_poly--Length(Rings) None mix_3_pt_line_poly--Length(VarBinary) None mix_3_pt_line_polyh--Data(Vertex) ComponentwiseDelta mix_3_pt_line_polyh--Length(Parts) None mix_3_pt_line_polyh--Length(Rings) None mix_3_pt_line_polyh--Length(VarBinary) None mix_3_pt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_3_pt_mline_mpoly--Length(Geometries) None mix_3_pt_mline_mpoly--Length(Parts) None mix_3_pt_mline_mpoly--Length(Rings) None mix_3_pt_mline_mpoly--Length(VarBinary) None mix_3_pt_mpt_mline--Data(Vertex) ComponentwiseDelta mix_3_pt_mpt_mline--Length(Geometries) None mix_3_pt_mpt_mline--Length(Parts) None mix_3_pt_mpt_mline--Length(VarBinary) None mix_3_pt_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_3_pt_mpt_mpoly--Length(Geometries) None mix_3_pt_mpt_mpoly--Length(Parts) None mix_3_pt_mpt_mpoly--Length(Rings) None mix_3_pt_mpt_mpoly--Length(VarBinary) None mix_3_pt_poly_mline--Data(Vertex) ComponentwiseDelta mix_3_pt_poly_mline--Length(Geometries) None mix_3_pt_poly_mline--Length(Parts) None mix_3_pt_poly_mline--Length(Rings) None mix_3_pt_poly_mline--Length(VarBinary) None mix_3_pt_poly_mpoly--Data(Vertex) ComponentwiseDelta mix_3_pt_poly_mpoly--Length(Geometries) None mix_3_pt_poly_mpoly--Length(Parts) None mix_3_pt_poly_mpoly--Length(Rings) None mix_3_pt_poly_mpoly--Length(VarBinary) None mix_3_pt_poly_mpt--Data(Vertex) ComponentwiseDelta mix_3_pt_poly_mpt--Length(Geometries) None mix_3_pt_poly_mpt--Length(Parts) None mix_3_pt_poly_mpt--Length(Rings) None mix_3_pt_poly_mpt--Length(VarBinary) None mix_3_pt_poly_polyh--Data(Vertex) ComponentwiseDelta mix_3_pt_poly_polyh--Length(Parts) None mix_3_pt_poly_polyh--Length(Rings) None mix_3_pt_poly_polyh--Length(VarBinary) None mix_3_pt_polyh_mline--Data(Vertex) ComponentwiseDelta mix_3_pt_polyh_mline--Length(Geometries) None mix_3_pt_polyh_mline--Length(Parts) None mix_3_pt_polyh_mline--Length(Rings) None mix_3_pt_polyh_mline--Length(VarBinary) None mix_3_pt_polyh_mpoly--Data(Vertex) ComponentwiseDelta mix_3_pt_polyh_mpoly--Length(Geometries) None mix_3_pt_polyh_mpoly--Length(Parts) None mix_3_pt_polyh_mpoly--Length(Rings) None mix_3_pt_polyh_mpoly--Length(VarBinary) None mix_3_pt_polyh_mpt--Data(Vertex) ComponentwiseDelta mix_3_pt_polyh_mpt--Length(Geometries) None mix_3_pt_polyh_mpt--Length(Parts) None mix_3_pt_polyh_mpt--Length(Rings) None mix_3_pt_polyh_mpt--Length(VarBinary) None mix_4_line_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_line_mpt_mline_mpoly--Length(Geometries) None mix_4_line_mpt_mline_mpoly--Length(Parts) None mix_4_line_mpt_mline_mpoly--Length(Rings) None mix_4_line_mpt_mline_mpoly--Length(VarBinary) None mix_4_line_poly_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_line_poly_mline_mpoly--Length(Geometries) None mix_4_line_poly_mline_mpoly--Length(Parts) None mix_4_line_poly_mline_mpoly--Length(Rings) None mix_4_line_poly_mline_mpoly--Length(VarBinary) None mix_4_line_poly_mpt_mline--Data(Vertex) ComponentwiseDelta mix_4_line_poly_mpt_mline--Length(Geometries) None mix_4_line_poly_mpt_mline--Length(Parts) None mix_4_line_poly_mpt_mline--Length(Rings) None mix_4_line_poly_mpt_mline--Length(VarBinary) None mix_4_line_poly_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_4_line_poly_mpt_mpoly--Length(Geometries) None mix_4_line_poly_mpt_mpoly--Length(Parts) None mix_4_line_poly_mpt_mpoly--Length(Rings) None mix_4_line_poly_mpt_mpoly--Length(VarBinary) None mix_4_line_poly_polyh_mline--Data(Vertex) ComponentwiseDelta mix_4_line_poly_polyh_mline--Length(Geometries) None mix_4_line_poly_polyh_mline--Length(Parts) None mix_4_line_poly_polyh_mline--Length(Rings) None mix_4_line_poly_polyh_mline--Length(VarBinary) None mix_4_line_poly_polyh_mpoly--Data(Vertex) ComponentwiseDelta mix_4_line_poly_polyh_mpoly--Length(Geometries) None mix_4_line_poly_polyh_mpoly--Length(Parts) None mix_4_line_poly_polyh_mpoly--Length(Rings) None mix_4_line_poly_polyh_mpoly--Length(VarBinary) None mix_4_line_poly_polyh_mpt--Data(Vertex) ComponentwiseDelta mix_4_line_poly_polyh_mpt--Length(Geometries) None mix_4_line_poly_polyh_mpt--Length(Parts) None mix_4_line_poly_polyh_mpt--Length(Rings) None mix_4_line_poly_polyh_mpt--Length(VarBinary) None mix_4_line_polyh_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_line_polyh_mline_mpoly--Length(Geometries) None mix_4_line_polyh_mline_mpoly--Length(Parts) None mix_4_line_polyh_mline_mpoly--Length(Rings) None mix_4_line_polyh_mline_mpoly--Length(VarBinary) None mix_4_line_polyh_mpt_mline--Data(Vertex) ComponentwiseDelta mix_4_line_polyh_mpt_mline--Length(Geometries) None mix_4_line_polyh_mpt_mline--Length(Parts) None mix_4_line_polyh_mpt_mline--Length(Rings) None mix_4_line_polyh_mpt_mline--Length(VarBinary) None mix_4_line_polyh_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_4_line_polyh_mpt_mpoly--Length(Geometries) None mix_4_line_polyh_mpt_mpoly--Length(Parts) None mix_4_line_polyh_mpt_mpoly--Length(Rings) None mix_4_line_polyh_mpt_mpoly--Length(VarBinary) None mix_4_poly_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_poly_mpt_mline_mpoly--Length(Geometries) None mix_4_poly_mpt_mline_mpoly--Length(Parts) None mix_4_poly_mpt_mline_mpoly--Length(Rings) None mix_4_poly_mpt_mline_mpoly--Length(VarBinary) None mix_4_poly_polyh_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_poly_polyh_mline_mpoly--Length(Geometries) None mix_4_poly_polyh_mline_mpoly--Length(Parts) None mix_4_poly_polyh_mline_mpoly--Length(Rings) None mix_4_poly_polyh_mline_mpoly--Length(VarBinary) None mix_4_poly_polyh_mpt_mline--Data(Vertex) ComponentwiseDelta mix_4_poly_polyh_mpt_mline--Length(Geometries) None mix_4_poly_polyh_mpt_mline--Length(Parts) None mix_4_poly_polyh_mpt_mline--Length(Rings) None mix_4_poly_polyh_mpt_mline--Length(VarBinary) None mix_4_poly_polyh_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_4_poly_polyh_mpt_mpoly--Length(Geometries) None mix_4_poly_polyh_mpt_mpoly--Length(Parts) None mix_4_poly_polyh_mpt_mpoly--Length(Rings) None mix_4_poly_polyh_mpt_mpoly--Length(VarBinary) None mix_4_polyh_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_polyh_mpt_mline_mpoly--Length(Geometries) None mix_4_polyh_mpt_mline_mpoly--Length(Parts) None mix_4_polyh_mpt_mline_mpoly--Length(Rings) None mix_4_polyh_mpt_mline_mpoly--Length(VarBinary) None mix_4_pt_line_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_line_mline_mpoly--Length(Geometries) None mix_4_pt_line_mline_mpoly--Length(Parts) None mix_4_pt_line_mline_mpoly--Length(Rings) None mix_4_pt_line_mline_mpoly--Length(VarBinary) None mix_4_pt_line_mpt_mline--Data(Vertex) ComponentwiseDelta mix_4_pt_line_mpt_mline--Length(Geometries) None mix_4_pt_line_mpt_mline--Length(Parts) None mix_4_pt_line_mpt_mline--Length(VarBinary) None mix_4_pt_line_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_line_mpt_mpoly--Length(Geometries) None mix_4_pt_line_mpt_mpoly--Length(Parts) None mix_4_pt_line_mpt_mpoly--Length(Rings) None mix_4_pt_line_mpt_mpoly--Length(VarBinary) None mix_4_pt_line_poly_mline--Data(Vertex) ComponentwiseDelta mix_4_pt_line_poly_mline--Length(Geometries) None mix_4_pt_line_poly_mline--Length(Parts) None mix_4_pt_line_poly_mline--Length(Rings) None mix_4_pt_line_poly_mline--Length(VarBinary) None mix_4_pt_line_poly_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_line_poly_mpoly--Length(Geometries) None mix_4_pt_line_poly_mpoly--Length(Parts) None mix_4_pt_line_poly_mpoly--Length(Rings) None mix_4_pt_line_poly_mpoly--Length(VarBinary) None mix_4_pt_line_poly_mpt--Data(Vertex) ComponentwiseDelta mix_4_pt_line_poly_mpt--Length(Geometries) None mix_4_pt_line_poly_mpt--Length(Parts) None mix_4_pt_line_poly_mpt--Length(Rings) None mix_4_pt_line_poly_mpt--Length(VarBinary) None mix_4_pt_line_poly_polyh--Data(Vertex) ComponentwiseDelta mix_4_pt_line_poly_polyh--Length(Parts) None mix_4_pt_line_poly_polyh--Length(Rings) None mix_4_pt_line_poly_polyh--Length(VarBinary) None mix_4_pt_line_polyh_mline--Data(Vertex) ComponentwiseDelta mix_4_pt_line_polyh_mline--Length(Geometries) None mix_4_pt_line_polyh_mline--Length(Parts) None mix_4_pt_line_polyh_mline--Length(Rings) None mix_4_pt_line_polyh_mline--Length(VarBinary) None mix_4_pt_line_polyh_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_line_polyh_mpoly--Length(Geometries) None mix_4_pt_line_polyh_mpoly--Length(Parts) None mix_4_pt_line_polyh_mpoly--Length(Rings) None mix_4_pt_line_polyh_mpoly--Length(VarBinary) None mix_4_pt_line_polyh_mpt--Data(Vertex) ComponentwiseDelta mix_4_pt_line_polyh_mpt--Length(Geometries) None mix_4_pt_line_polyh_mpt--Length(Parts) None mix_4_pt_line_polyh_mpt--Length(Rings) None mix_4_pt_line_polyh_mpt--Length(VarBinary) None mix_4_pt_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_mpt_mline_mpoly--Length(Geometries) None mix_4_pt_mpt_mline_mpoly--Length(Parts) None mix_4_pt_mpt_mline_mpoly--Length(Rings) None mix_4_pt_mpt_mline_mpoly--Length(VarBinary) None mix_4_pt_poly_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_poly_mline_mpoly--Length(Geometries) None mix_4_pt_poly_mline_mpoly--Length(Parts) None mix_4_pt_poly_mline_mpoly--Length(Rings) None mix_4_pt_poly_mline_mpoly--Length(VarBinary) None mix_4_pt_poly_mpt_mline--Data(Vertex) ComponentwiseDelta mix_4_pt_poly_mpt_mline--Length(Geometries) None mix_4_pt_poly_mpt_mline--Length(Parts) None mix_4_pt_poly_mpt_mline--Length(Rings) None mix_4_pt_poly_mpt_mline--Length(VarBinary) None mix_4_pt_poly_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_poly_mpt_mpoly--Length(Geometries) None mix_4_pt_poly_mpt_mpoly--Length(Parts) None mix_4_pt_poly_mpt_mpoly--Length(Rings) None mix_4_pt_poly_mpt_mpoly--Length(VarBinary) None mix_4_pt_poly_polyh_mline--Data(Vertex) ComponentwiseDelta mix_4_pt_poly_polyh_mline--Length(Geometries) None mix_4_pt_poly_polyh_mline--Length(Parts) None mix_4_pt_poly_polyh_mline--Length(Rings) None mix_4_pt_poly_polyh_mline--Length(VarBinary) None mix_4_pt_poly_polyh_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_poly_polyh_mpoly--Length(Geometries) None mix_4_pt_poly_polyh_mpoly--Length(Parts) None mix_4_pt_poly_polyh_mpoly--Length(Rings) None mix_4_pt_poly_polyh_mpoly--Length(VarBinary) None mix_4_pt_poly_polyh_mpt--Data(Vertex) ComponentwiseDelta mix_4_pt_poly_polyh_mpt--Length(Geometries) None mix_4_pt_poly_polyh_mpt--Length(Parts) None mix_4_pt_poly_polyh_mpt--Length(Rings) None mix_4_pt_poly_polyh_mpt--Length(VarBinary) None mix_4_pt_polyh_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_polyh_mline_mpoly--Length(Geometries) None mix_4_pt_polyh_mline_mpoly--Length(Parts) None mix_4_pt_polyh_mline_mpoly--Length(Rings) None mix_4_pt_polyh_mline_mpoly--Length(VarBinary) None mix_4_pt_polyh_mpt_mline--Data(Vertex) ComponentwiseDelta mix_4_pt_polyh_mpt_mline--Length(Geometries) None mix_4_pt_polyh_mpt_mline--Length(Parts) None mix_4_pt_polyh_mpt_mline--Length(Rings) None mix_4_pt_polyh_mpt_mline--Length(VarBinary) None mix_4_pt_polyh_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_4_pt_polyh_mpt_mpoly--Length(Geometries) None mix_4_pt_polyh_mpt_mpoly--Length(Parts) None mix_4_pt_polyh_mpt_mpoly--Length(Rings) None mix_4_pt_polyh_mpt_mpoly--Length(VarBinary) None mix_5_line_poly_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_line_poly_mpt_mline_mpoly--Length(Geometries) None mix_5_line_poly_mpt_mline_mpoly--Length(Parts) None mix_5_line_poly_mpt_mline_mpoly--Length(Rings) None mix_5_line_poly_mpt_mline_mpoly--Length(VarBinary) None mix_5_line_poly_polyh_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_line_poly_polyh_mline_mpoly--Length(Geometries) None mix_5_line_poly_polyh_mline_mpoly--Length(Parts) None mix_5_line_poly_polyh_mline_mpoly--Length(Rings) None mix_5_line_poly_polyh_mline_mpoly--Length(VarBinary) None mix_5_line_poly_polyh_mpt_mline--Data(Vertex) ComponentwiseDelta mix_5_line_poly_polyh_mpt_mline--Length(Geometries) None mix_5_line_poly_polyh_mpt_mline--Length(Parts) None mix_5_line_poly_polyh_mpt_mline--Length(Rings) None mix_5_line_poly_polyh_mpt_mline--Length(VarBinary) None mix_5_line_poly_polyh_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_5_line_poly_polyh_mpt_mpoly--Length(Geometries) None mix_5_line_poly_polyh_mpt_mpoly--Length(Parts) None mix_5_line_poly_polyh_mpt_mpoly--Length(Rings) None mix_5_line_poly_polyh_mpt_mpoly--Length(VarBinary) None mix_5_line_polyh_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_line_polyh_mpt_mline_mpoly--Length(Geometries) None mix_5_line_polyh_mpt_mline_mpoly--Length(Parts) None mix_5_line_polyh_mpt_mline_mpoly--Length(Rings) None mix_5_line_polyh_mpt_mline_mpoly--Length(VarBinary) None mix_5_poly_polyh_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_poly_polyh_mpt_mline_mpoly--Length(Geometries) None mix_5_poly_polyh_mpt_mline_mpoly--Length(Parts) None mix_5_poly_polyh_mpt_mline_mpoly--Length(Rings) None mix_5_poly_polyh_mpt_mline_mpoly--Length(VarBinary) None mix_5_pt_line_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_line_mpt_mline_mpoly--Length(Geometries) None mix_5_pt_line_mpt_mline_mpoly--Length(Parts) None mix_5_pt_line_mpt_mline_mpoly--Length(Rings) None mix_5_pt_line_mpt_mline_mpoly--Length(VarBinary) None mix_5_pt_line_poly_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_line_poly_mline_mpoly--Length(Geometries) None mix_5_pt_line_poly_mline_mpoly--Length(Parts) None mix_5_pt_line_poly_mline_mpoly--Length(Rings) None mix_5_pt_line_poly_mline_mpoly--Length(VarBinary) None mix_5_pt_line_poly_mpt_mline--Data(Vertex) ComponentwiseDelta mix_5_pt_line_poly_mpt_mline--Length(Geometries) None mix_5_pt_line_poly_mpt_mline--Length(Parts) None mix_5_pt_line_poly_mpt_mline--Length(Rings) None mix_5_pt_line_poly_mpt_mline--Length(VarBinary) None mix_5_pt_line_poly_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_line_poly_mpt_mpoly--Length(Geometries) None mix_5_pt_line_poly_mpt_mpoly--Length(Parts) None mix_5_pt_line_poly_mpt_mpoly--Length(Rings) None mix_5_pt_line_poly_mpt_mpoly--Length(VarBinary) None mix_5_pt_line_poly_polyh_mline--Data(Vertex) ComponentwiseDelta mix_5_pt_line_poly_polyh_mline--Length(Geometries) None mix_5_pt_line_poly_polyh_mline--Length(Parts) None mix_5_pt_line_poly_polyh_mline--Length(Rings) None mix_5_pt_line_poly_polyh_mline--Length(VarBinary) None mix_5_pt_line_poly_polyh_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_line_poly_polyh_mpoly--Length(Geometries) None mix_5_pt_line_poly_polyh_mpoly--Length(Parts) None mix_5_pt_line_poly_polyh_mpoly--Length(Rings) None mix_5_pt_line_poly_polyh_mpoly--Length(VarBinary) None mix_5_pt_line_poly_polyh_mpt--Data(Vertex) ComponentwiseDelta mix_5_pt_line_poly_polyh_mpt--Length(Geometries) None mix_5_pt_line_poly_polyh_mpt--Length(Parts) None mix_5_pt_line_poly_polyh_mpt--Length(Rings) None mix_5_pt_line_poly_polyh_mpt--Length(VarBinary) None mix_5_pt_line_polyh_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_line_polyh_mline_mpoly--Length(Geometries) None mix_5_pt_line_polyh_mline_mpoly--Length(Parts) None mix_5_pt_line_polyh_mline_mpoly--Length(Rings) None mix_5_pt_line_polyh_mline_mpoly--Length(VarBinary) None mix_5_pt_line_polyh_mpt_mline--Data(Vertex) ComponentwiseDelta mix_5_pt_line_polyh_mpt_mline--Length(Geometries) None mix_5_pt_line_polyh_mpt_mline--Length(Parts) None mix_5_pt_line_polyh_mpt_mline--Length(Rings) None mix_5_pt_line_polyh_mpt_mline--Length(VarBinary) None mix_5_pt_line_polyh_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_line_polyh_mpt_mpoly--Length(Geometries) None mix_5_pt_line_polyh_mpt_mpoly--Length(Parts) None mix_5_pt_line_polyh_mpt_mpoly--Length(Rings) None mix_5_pt_line_polyh_mpt_mpoly--Length(VarBinary) None mix_5_pt_poly_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_poly_mpt_mline_mpoly--Length(Geometries) None mix_5_pt_poly_mpt_mline_mpoly--Length(Parts) None mix_5_pt_poly_mpt_mline_mpoly--Length(Rings) None mix_5_pt_poly_mpt_mline_mpoly--Length(VarBinary) None mix_5_pt_poly_polyh_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_poly_polyh_mline_mpoly--Length(Geometries) None mix_5_pt_poly_polyh_mline_mpoly--Length(Parts) None mix_5_pt_poly_polyh_mline_mpoly--Length(Rings) None mix_5_pt_poly_polyh_mline_mpoly--Length(VarBinary) None mix_5_pt_poly_polyh_mpt_mline--Data(Vertex) ComponentwiseDelta mix_5_pt_poly_polyh_mpt_mline--Length(Geometries) None mix_5_pt_poly_polyh_mpt_mline--Length(Parts) None mix_5_pt_poly_polyh_mpt_mline--Length(Rings) None mix_5_pt_poly_polyh_mpt_mline--Length(VarBinary) None mix_5_pt_poly_polyh_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_poly_polyh_mpt_mpoly--Length(Geometries) None mix_5_pt_poly_polyh_mpt_mpoly--Length(Parts) None mix_5_pt_poly_polyh_mpt_mpoly--Length(Rings) None mix_5_pt_poly_polyh_mpt_mpoly--Length(VarBinary) None mix_5_pt_polyh_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_5_pt_polyh_mpt_mline_mpoly--Length(Geometries) None mix_5_pt_polyh_mpt_mline_mpoly--Length(Parts) None mix_5_pt_polyh_mpt_mline_mpoly--Length(Rings) None mix_5_pt_polyh_mpt_mline_mpoly--Length(VarBinary) None mix_6_line_poly_polyh_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_6_line_poly_polyh_mpt_mline_mpoly--Length(Geometries) None mix_6_line_poly_polyh_mpt_mline_mpoly--Length(Parts) None mix_6_line_poly_polyh_mpt_mline_mpoly--Length(Rings) None mix_6_line_poly_polyh_mpt_mline_mpoly--Length(VarBinary) None mix_6_pt_line_poly_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_6_pt_line_poly_mpt_mline_mpoly--Length(Geometries) None mix_6_pt_line_poly_mpt_mline_mpoly--Length(Parts) None mix_6_pt_line_poly_mpt_mline_mpoly--Length(Rings) None mix_6_pt_line_poly_mpt_mline_mpoly--Length(VarBinary) None mix_6_pt_line_poly_polyh_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_6_pt_line_poly_polyh_mline_mpoly--Length(Geometries) None mix_6_pt_line_poly_polyh_mline_mpoly--Length(Parts) None mix_6_pt_line_poly_polyh_mline_mpoly--Length(Rings) None mix_6_pt_line_poly_polyh_mline_mpoly--Length(VarBinary) None mix_6_pt_line_poly_polyh_mpt_mline--Data(Vertex) ComponentwiseDelta mix_6_pt_line_poly_polyh_mpt_mline--Length(Geometries) None mix_6_pt_line_poly_polyh_mpt_mline--Length(Parts) None mix_6_pt_line_poly_polyh_mpt_mline--Length(Rings) None mix_6_pt_line_poly_polyh_mpt_mline--Length(VarBinary) None mix_6_pt_line_poly_polyh_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_6_pt_line_poly_polyh_mpt_mpoly--Length(Geometries) None mix_6_pt_line_poly_polyh_mpt_mpoly--Length(Parts) None mix_6_pt_line_poly_polyh_mpt_mpoly--Length(Rings) None mix_6_pt_line_poly_polyh_mpt_mpoly--Length(VarBinary) None mix_6_pt_line_polyh_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_6_pt_line_polyh_mpt_mline_mpoly--Length(Geometries) None mix_6_pt_line_polyh_mpt_mline_mpoly--Length(Parts) None mix_6_pt_line_polyh_mpt_mline_mpoly--Length(Rings) None mix_6_pt_line_polyh_mpt_mline_mpoly--Length(VarBinary) None mix_6_pt_poly_polyh_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_6_pt_poly_polyh_mpt_mline_mpoly--Length(Geometries) None mix_6_pt_poly_polyh_mpt_mline_mpoly--Length(Parts) None mix_6_pt_poly_polyh_mpt_mline_mpoly--Length(Rings) None mix_6_pt_poly_polyh_mpt_mline_mpoly--Length(VarBinary) None mix_7_pt_line_poly_polyh_mpt_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_7_pt_line_poly_polyh_mpt_mline_mpoly--Length(Geometries) None mix_7_pt_line_poly_polyh_mpt_mline_mpoly--Length(Parts) None mix_7_pt_line_poly_polyh_mpt_mline_mpoly--Length(Rings) None mix_7_pt_line_poly_polyh_mpt_mline_mpoly--Length(VarBinary) None mix_dup_line--Data(Vertex) ComponentwiseDelta mix_dup_line--Length(Parts) None mix_dup_line--Length(VarBinary) None mix_dup_mline--Data(Vertex) ComponentwiseDelta mix_dup_mline--Length(Geometries) None mix_dup_mline--Length(Parts) None mix_dup_mline--Length(VarBinary) None mix_dup_mpoly--Data(Vertex) ComponentwiseDelta mix_dup_mpoly--Length(Geometries) None mix_dup_mpoly--Length(Parts) None mix_dup_mpoly--Length(Rings) None mix_dup_mpoly--Length(VarBinary) None mix_dup_mpt--Data(Vertex) ComponentwiseDelta mix_dup_mpt--Length(Geometries) None mix_dup_mpt--Length(VarBinary) None mix_dup_poly--Data(Vertex) ComponentwiseDelta mix_dup_poly--Length(Parts) None mix_dup_poly--Length(Rings) None mix_dup_poly--Length(VarBinary) None mix_dup_polyh--Data(Vertex) ComponentwiseDelta mix_dup_polyh--Length(Parts) None mix_dup_polyh--Length(Rings) None mix_dup_polyh--Length(VarBinary) None mix_dup_pt--Data(Vertex) ComponentwiseDelta mix_dup_pt--Length(VarBinary) None mix_line_mline_line--Data(Vertex) ComponentwiseDelta mix_line_mline_line--Length(Geometries) None mix_line_mline_line--Length(Parts) None mix_line_mline_line--Length(VarBinary) None mix_line_mpoly_line--Data(Vertex) ComponentwiseDelta mix_line_mpoly_line--Length(Geometries) None mix_line_mpoly_line--Length(Parts) None mix_line_mpoly_line--Length(Rings) None mix_line_mpoly_line--Length(VarBinary) None mix_line_mpt_line--Data(Vertex) ComponentwiseDelta mix_line_mpt_line--Length(Geometries) None mix_line_mpt_line--Length(Parts) None mix_line_mpt_line--Length(VarBinary) None mix_line_poly_line--Data(Vertex) ComponentwiseDelta mix_line_poly_line--Length(Parts) None mix_line_poly_line--Length(Rings) None mix_line_poly_line--Length(VarBinary) None mix_line_polyh_line--Data(Vertex) ComponentwiseDelta mix_line_polyh_line--Length(Parts) None mix_line_polyh_line--Length(Rings) None mix_line_polyh_line--Length(VarBinary) None mix_line_pt_line--Data(Vertex) ComponentwiseDelta mix_line_pt_line--Length(Parts) None mix_line_pt_line--Length(VarBinary) None mix_mline_line_mline--Data(Vertex) ComponentwiseDelta mix_mline_line_mline--Length(Geometries) None mix_mline_line_mline--Length(Parts) None mix_mline_line_mline--Length(VarBinary) None mix_mline_mpoly_mline--Data(Vertex) ComponentwiseDelta mix_mline_mpoly_mline--Length(Geometries) None mix_mline_mpoly_mline--Length(Parts) None mix_mline_mpoly_mline--Length(Rings) None mix_mline_mpoly_mline--Length(VarBinary) None mix_mline_mpt_mline--Data(Vertex) ComponentwiseDelta mix_mline_mpt_mline--Length(Geometries) None mix_mline_mpt_mline--Length(Parts) None mix_mline_mpt_mline--Length(VarBinary) None mix_mline_poly_mline--Data(Vertex) ComponentwiseDelta mix_mline_poly_mline--Length(Geometries) None mix_mline_poly_mline--Length(Parts) None mix_mline_poly_mline--Length(Rings) None mix_mline_poly_mline--Length(VarBinary) None mix_mline_polyh_mline--Data(Vertex) ComponentwiseDelta mix_mline_polyh_mline--Length(Geometries) None mix_mline_polyh_mline--Length(Parts) None mix_mline_polyh_mline--Length(Rings) None mix_mline_polyh_mline--Length(VarBinary) None mix_mline_pt_mline--Data(Vertex) ComponentwiseDelta mix_mline_pt_mline--Length(Geometries) None mix_mline_pt_mline--Length(Parts) None mix_mline_pt_mline--Length(VarBinary) None mix_mpoly_line_mpoly--Data(Vertex) ComponentwiseDelta mix_mpoly_line_mpoly--Length(Geometries) None mix_mpoly_line_mpoly--Length(Parts) None mix_mpoly_line_mpoly--Length(Rings) None mix_mpoly_line_mpoly--Length(VarBinary) None mix_mpoly_mline_mpoly--Data(Vertex) ComponentwiseDelta mix_mpoly_mline_mpoly--Length(Geometries) None mix_mpoly_mline_mpoly--Length(Parts) None mix_mpoly_mline_mpoly--Length(Rings) None mix_mpoly_mline_mpoly--Length(VarBinary) None mix_mpoly_mpt_mpoly--Data(Vertex) ComponentwiseDelta mix_mpoly_mpt_mpoly--Length(Geometries) None mix_mpoly_mpt_mpoly--Length(Parts) None mix_mpoly_mpt_mpoly--Length(Rings) None mix_mpoly_mpt_mpoly--Length(VarBinary) None mix_mpoly_poly_mpoly--Data(Vertex) ComponentwiseDelta mix_mpoly_poly_mpoly--Length(Geometries) None mix_mpoly_poly_mpoly--Length(Parts) None mix_mpoly_poly_mpoly--Length(Rings) None mix_mpoly_poly_mpoly--Length(VarBinary) None mix_mpoly_polyh_mpoly--Data(Vertex) ComponentwiseDelta mix_mpoly_polyh_mpoly--Length(Geometries) None mix_mpoly_polyh_mpoly--Length(Parts) None mix_mpoly_polyh_mpoly--Length(Rings) None mix_mpoly_polyh_mpoly--Length(VarBinary) None mix_mpoly_pt_mpoly--Data(Vertex) ComponentwiseDelta mix_mpoly_pt_mpoly--Length(Geometries) None mix_mpoly_pt_mpoly--Length(Parts) None mix_mpoly_pt_mpoly--Length(Rings) None mix_mpoly_pt_mpoly--Length(VarBinary) None mix_mpt_line_mpt--Data(Vertex) ComponentwiseDelta mix_mpt_line_mpt--Length(Geometries) None mix_mpt_line_mpt--Length(Parts) None mix_mpt_line_mpt--Length(VarBinary) None mix_mpt_mline_mpt--Data(Vertex) ComponentwiseDelta mix_mpt_mline_mpt--Length(Geometries) None mix_mpt_mline_mpt--Length(Parts) None mix_mpt_mline_mpt--Length(VarBinary) None mix_mpt_mpoly_mpt--Data(Vertex) ComponentwiseDelta mix_mpt_mpoly_mpt--Length(Geometries) None mix_mpt_mpoly_mpt--Length(Parts) None mix_mpt_mpoly_mpt--Length(Rings) None mix_mpt_mpoly_mpt--Length(VarBinary) None mix_mpt_poly_mpt--Data(Vertex) ComponentwiseDelta mix_mpt_poly_mpt--Length(Geometries) None mix_mpt_poly_mpt--Length(Parts) None mix_mpt_poly_mpt--Length(Rings) None mix_mpt_poly_mpt--Length(VarBinary) None mix_mpt_polyh_mpt--Data(Vertex) ComponentwiseDelta mix_mpt_polyh_mpt--Length(Geometries) None mix_mpt_polyh_mpt--Length(Parts) None mix_mpt_polyh_mpt--Length(Rings) None mix_mpt_polyh_mpt--Length(VarBinary) None mix_mpt_pt_mpt--Data(Vertex) ComponentwiseDelta mix_mpt_pt_mpt--Length(Geometries) None mix_mpt_pt_mpt--Length(VarBinary) None mix_poly_line_poly--Data(Vertex) ComponentwiseDelta mix_poly_line_poly--Length(Parts) None mix_poly_line_poly--Length(Rings) None mix_poly_line_poly--Length(VarBinary) None mix_poly_mline_poly--Data(Vertex) ComponentwiseDelta mix_poly_mline_poly--Length(Geometries) None mix_poly_mline_poly--Length(Parts) None mix_poly_mline_poly--Length(Rings) None mix_poly_mline_poly--Length(VarBinary) None mix_poly_mpoly_poly--Data(Vertex) ComponentwiseDelta mix_poly_mpoly_poly--Length(Geometries) None mix_poly_mpoly_poly--Length(Parts) None mix_poly_mpoly_poly--Length(Rings) None mix_poly_mpoly_poly--Length(VarBinary) None mix_poly_mpt_poly--Data(Vertex) ComponentwiseDelta mix_poly_mpt_poly--Length(Geometries) None mix_poly_mpt_poly--Length(Parts) None mix_poly_mpt_poly--Length(Rings) None mix_poly_mpt_poly--Length(VarBinary) None mix_poly_polyh_poly--Data(Vertex) ComponentwiseDelta mix_poly_polyh_poly--Length(Parts) None mix_poly_polyh_poly--Length(Rings) None mix_poly_polyh_poly--Length(VarBinary) None mix_poly_pt_poly--Data(Vertex) ComponentwiseDelta mix_poly_pt_poly--Length(Parts) None mix_poly_pt_poly--Length(Rings) None mix_poly_pt_poly--Length(VarBinary) None mix_polyh_line_polyh--Data(Vertex) ComponentwiseDelta mix_polyh_line_polyh--Length(Parts) None mix_polyh_line_polyh--Length(Rings) None mix_polyh_line_polyh--Length(VarBinary) None mix_polyh_mline_polyh--Data(Vertex) ComponentwiseDelta mix_polyh_mline_polyh--Length(Geometries) None mix_polyh_mline_polyh--Length(Parts) None mix_polyh_mline_polyh--Length(Rings) None mix_polyh_mline_polyh--Length(VarBinary) None mix_polyh_mpoly_polyh--Data(Vertex) ComponentwiseDelta mix_polyh_mpoly_polyh--Length(Geometries) None mix_polyh_mpoly_polyh--Length(Parts) None mix_polyh_mpoly_polyh--Length(Rings) None mix_polyh_mpoly_polyh--Length(VarBinary) None mix_polyh_mpt_polyh--Data(Vertex) ComponentwiseDelta mix_polyh_mpt_polyh--Length(Geometries) None mix_polyh_mpt_polyh--Length(Parts) None mix_polyh_mpt_polyh--Length(Rings) None mix_polyh_mpt_polyh--Length(VarBinary) None mix_polyh_poly_polyh--Data(Vertex) ComponentwiseDelta mix_polyh_poly_polyh--Length(Parts) None mix_polyh_poly_polyh--Length(Rings) None mix_polyh_poly_polyh--Length(VarBinary) None mix_polyh_pt_polyh--Data(Vertex) ComponentwiseDelta mix_polyh_pt_polyh--Length(Parts) None mix_polyh_pt_polyh--Length(Rings) None mix_polyh_pt_polyh--Length(VarBinary) None mix_pt_line_pt--Data(Vertex) ComponentwiseDelta mix_pt_line_pt--Length(Parts) None mix_pt_line_pt--Length(VarBinary) None mix_pt_mline_pt--Data(Vertex) ComponentwiseDelta mix_pt_mline_pt--Length(Geometries) None mix_pt_mline_pt--Length(Parts) None mix_pt_mline_pt--Length(VarBinary) None mix_pt_mpoly_pt--Data(Vertex) ComponentwiseDelta mix_pt_mpoly_pt--Length(Geometries) None mix_pt_mpoly_pt--Length(Parts) None mix_pt_mpoly_pt--Length(Rings) None mix_pt_mpoly_pt--Length(VarBinary) None mix_pt_mpt_pt--Data(Vertex) ComponentwiseDelta mix_pt_mpt_pt--Length(Geometries) None mix_pt_mpt_pt--Length(VarBinary) None mix_pt_poly_pt--Data(Vertex) ComponentwiseDelta mix_pt_poly_pt--Length(Parts) None mix_pt_poly_pt--Length(Rings) None mix_pt_poly_pt--Length(VarBinary) None mix_pt_polyh_pt--Data(Vertex) ComponentwiseDelta mix_pt_polyh_pt--Length(Parts) None mix_pt_polyh_pt--Length(Rings) None mix_pt_polyh_pt--Length(VarBinary) None ================================================ FILE: test/synthetic/synthetic-test-utils/index.ts ================================================ import { globSync, readFileSync, writeFileSync } from "node:fs"; import * as path from "node:path"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); const RELATIVE_FLOAT_TOLERANCE = 0.0001 / 100; const ABSOLUTE_FLOAT_TOLERANCE = Number.EPSILON; export function compareWithTolerance( received: unknown, expected: unknown, ): boolean | undefined { if (typeof expected === "string") { if (expected.endsWith("NAN")) { expected = Number.NaN; } else if (expected.endsWith("INFINITY")) { expected = expected.endsWith("NEG_INFINITY") ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY; } } if (typeof received !== "number" || typeof expected !== "number") { return undefined; } if (!Number.isFinite(expected)) return Object.is(received, expected); if (Math.abs(expected) < ABSOLUTE_FLOAT_TOLERANCE) { return Math.abs(received) <= ABSOLUTE_FLOAT_TOLERANCE; } const relativeError = Math.abs(received - expected) / Math.abs(expected); return relativeError <= RELATIVE_FLOAT_TOLERANCE; } export function writeActualOutput( mltFile: string, actual: Record, ): string { const actualFile = mltFile.replace(/\.mlt$/, ".actual.json"); writeFileSync(actualFile, `${JSON.stringify(actual, null, 2)}\n`, "utf-8"); return actualFile; } export function getTestCases(skipList: string[]): { active: { name: string; content: object; fileName: string }[]; skipped: string[]; } { const syntheticDir = resolve(__dirname, ".."); const mltFiles = globSync(`**/*.mlt`, { cwd: syntheticDir, }).map((mltFile: string) => path.join(syntheticDir, mltFile)); const active: { name: string; content: object; fileName: string }[] = []; const skipped: string[] = []; for (const mltFile of mltFiles) { const testName = path.relative(syntheticDir, mltFile).replace(/\.mlt$/, ""); if (skipList.includes(testName)) { skipped.push(testName); } else { const jsonFile = mltFile.replace(/\.mlt$/, ".json"); const expectedRaw = readFileSync(jsonFile, "utf-8"); const expected = JSON.parse(expectedRaw); active.push({ name: testName, fileName: mltFile, content: expected }); } } return { active, skipped }; } ================================================ FILE: test/synthetic/synthetic-test-utils/package.json ================================================ { "name": "synthetic-test-utils", "main": "index.ts", "type": "module", "dependencies": { } } ================================================ FILE: ts/.gitignore ================================================ dist/ LICENSE.txt ================================================ FILE: ts/.nvmrc ================================================ 24.11 ================================================ FILE: ts/README.md ================================================ # maplibre-tile-spec This package contains a JavaScript decoder for the experimental MapLibre Tile (MLT) vector tile format. ## Install `npm install @maplibre/maplibre-tile-spec` ## Quickstart To decode a tile, you will want to load `MltDecoder`: ```js import { decodeTile } from '@maplibre/maplibre-tile-spec'; const data = fs.readFileSync(tilePath); const tile = decodeTile(data); ``` ## Contents ### Code Code is in `src/`. ### Tests Tests are in `test/unit/`. Run tests by running `npm run test`. currently not integrated ================================================ FILE: ts/RELEASE.md ================================================ # Release to NPM 1. Run the `js-bump-version.yml` workflow. Select the appropiate type of version bump. 2. Merge the PR this workflow creates. The `release-js.yml` workflow will now create a release. ================================================ FILE: ts/biome.json ================================================ { "root": false, "$schema": "https://biomejs.dev/schemas/2.0.5/schema.json", "javascript": { "formatter": { "indentWidth": 4, "indentStyle": "space", "lineWidth": 120 } }, "linter": { "rules": { "suspicious": { "noExplicitAny": "off", "noImplicitAnyLet": "off", "noShadowRestrictedNames": "off" }, "style": { "noParameterAssign": "off" } } }, "assist": { "actions": { "source": { "organizeImports": "off" } } } } ================================================ FILE: ts/buf.gen.yaml ================================================ version: v1 plugins: - plugin: es opt: target=ts out: src/metadata/tileset ================================================ FILE: ts/eslint.config.mjs ================================================ import eslint from "@eslint/js"; import tseslint from "typescript-eslint"; export default tseslint.config( eslint.configs.recommended, ...tseslint.configs.recommended, ...tseslint.configs.recommendedTypeChecked, ...tseslint.configs.strict, { languageOptions: { parserOptions: { project: "./tsconfig.lint.json", tsconfigRootDir: import.meta.dirname, }, }, rules: { // All the below are disabled, but they need to be enabled at some point... "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-unsafe-argument": "off", "@typescript-eslint/no-unsafe-call": "off", "@typescript-eslint/no-unsafe-member-access": "off", "@typescript-eslint/no-unsafe-return": "off", "@typescript-eslint/no-unsafe-assignment": "off", "@typescript-eslint/no-extraneous-class": "off", "@typescript-eslint/prefer-literal-enum-member": "off", "@typescript-eslint/restrict-template-expressions": "off", "no-case-declarations": "off", "@typescript-eslint/no-base-to-string": "off", "@typescript-eslint/no-unsafe-enum-comparison": "off", "@typescript-eslint/no-dynamic-delete": "off", "@typescript-eslint/no-unused-vars": "off", //'@typescript-eslint/no-unused-vars': ['error', { // argsIgnorePattern: '^_', // varsIgnorePattern: '^_' //}], "@typescript-eslint/consistent-type-imports": [ "error", { prefer: "type-imports", fixStyle: "inline-type-imports", }, ], }, }, { files: ["**/*.spec.ts"], rules: { "@typescript-eslint/unbound-method": "off", }, }, { ignores: ["dist", "node_modules", "coverage", "*.config.mjs", "*.config.js", "**/*.js"], }, ); ================================================ FILE: ts/mod.just ================================================ just := quote(just_executable()) _default: (just '--list' 'ts') [private] just *args: {{just}} {{args}} # Build the package build: install npm run build # Quick compile/type check check: install npm run build # Run all CI steps: lint, test, build ci-test: lint test build {{just}} assert-git-is-clean # Delete build artifacts clean: rm -rf node_modules dist # Reformat code fmt: install npm run format # Install dependencies install: npm ci # Run linting lint: install npm run lint # Run tests test: install npm run test # Run benchmark bench: install echo "TODO: Add js benchmark command" ================================================ FILE: ts/package.json ================================================ { "name": "@maplibre/mlt", "version": "1.1.9", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ "/dist" ], "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/maplibre/maplibre-tile-spec/#readme", "keywords": [ "maplibre", "gis", "vector" ], "scripts": { "build": "tsc", "test": "vitest run --coverage --coverage.reportOnFailure", "lint": "eslint" }, "repository": { "type": "git", "url": "https://github.com/maplibre/maplibre-tile-spec" }, "bugs": { "url": "https://github.com/maplibre/maplibre-tile-spec/issues" }, "devDependencies": { "@eslint/js": "^10.0.1", "@mapbox/vector-tile": "^2.0.4", "@maplibre/maplibre-gl-style-spec": "^24.7.0", "@types/bytebuffer": "^5.0.49", "@types/earcut": "^3.0.0", "@types/node": "^25.5.0", "@types/pbf": "^3.0.5", "@vitest/coverage-v8": "^4.1.0", "bytebuffer": "^5.0.1", "earcut": "^3.0.2", "eslint": "^10.0.3", "pbf": "^4.0.1", "typescript": "^5.9.3", "typescript-eslint": "^8.57.1", "vitest": "^4.0.1" }, "dependencies": { "@mapbox/point-geometry": "^1.1.0" } } ================================================ FILE: ts/src/decoding/bigEndianDecode.spec.ts ================================================ import { describe, expect, it } from "vitest"; import { decodeBigEndianInt32sInto } from "./bigEndianDecode"; import { encodeBigEndianInt32s } from "../encoding/bigEndianEncode"; function decodeThenEncode(bytes: Uint8Array, offset: number, byteLength: number): Uint8Array { const out = new Int32Array(Math.ceil(byteLength / 4)); const written = decodeBigEndianInt32sInto(bytes, offset, byteLength, out); return encodeBigEndianInt32s(out.subarray(0, written)); } function expectDecodeEncodeRoundTrip(bytes: Uint8Array, offset: number, byteLength: number): Uint8Array { const encoded = decodeThenEncode(bytes, offset, byteLength); const expected = bytes.subarray(offset, offset + byteLength); expect(encoded.subarray(0, byteLength)).toEqual(expected); for (let i = byteLength; i < encoded.length; i++) expect(encoded[i]).toBe(0); expect(encoded.length).toBe(Math.ceil(byteLength / 4) * 4); return encoded; } describe("decodeBigEndianInt32s", () => { it("round-trips bytes (aligned offset)", () => { const bytes = new Uint8Array([0x12, 0x34, 0x56, 0x78, 0xff, 0xff, 0xff, 0xff]); expectDecodeEncodeRoundTrip(bytes, 0, bytes.length); }); it("round-trips bytes (unaligned offset)", () => { const buffer = new Uint8Array(10); buffer[1] = 0x00; buffer[2] = 0x00; buffer[3] = 0x00; buffer[4] = 0x42; expectDecodeEncodeRoundTrip(buffer, 1, 4); }); it("round-trips bytes with trailing bytes (length not multiple of 4)", () => { const bytes = new Uint8Array([0x00, 0x00, 0x01, 0x00, 0xab]); expectDecodeEncodeRoundTrip(bytes, 0, 5); }); it("round-trips bytes with 1-3 trailing bytes", () => { expectDecodeEncodeRoundTrip(new Uint8Array([0xaa]), 0, 1); expectDecodeEncodeRoundTrip(new Uint8Array([0xaa, 0xbb]), 0, 2); expectDecodeEncodeRoundTrip(new Uint8Array([0xaa, 0xbb, 0xcc]), 0, 3); }); it("throws on out of bounds", () => { const bytes = new Uint8Array(4); expect(() => decodeBigEndianInt32sInto(bytes, 0, 5, new Int32Array(2))).toThrow(); expect(() => decodeBigEndianInt32sInto(bytes, -1, 4, new Int32Array(1))).toThrow(); }); it("round-trips with encodeBigEndianInt32s (aligned)", () => { const input = new Int32Array([0, 1, -1, 0x12345678]); const bytes = encodeBigEndianInt32s(input); const out = new Int32Array(input.length); const written = decodeBigEndianInt32sInto(bytes, 0, bytes.length, out); expect(written).toBe(input.length); expect(out).toEqual(input); }); it("round-trips with encodeBigEndianInt32s (unaligned view)", () => { const input = new Int32Array([0x01020304, -123456789, 0, 42]); const bytes = encodeBigEndianInt32s(input); const buffer = new Uint8Array(bytes.length + 3); buffer.set([0xaa, 0xbb, 0xcc], 0); buffer.set(bytes, 3); const out = new Int32Array(input.length); const written = decodeBigEndianInt32sInto(buffer, 3, bytes.length, out); expect(written).toBe(input.length); expect(out).toEqual(input); }); it("decodes into a provided buffer", () => { const input = new Int32Array([0x01020304, -123456789, 0, 42]); const bytes = encodeBigEndianInt32s(input); const out = new Int32Array(input.length + 8); const written = decodeBigEndianInt32sInto(bytes, 0, bytes.length, out); expect(written).toBe(input.length); const encoded = encodeBigEndianInt32s(out.subarray(0, written)); expect(encoded).toEqual(bytes); }); it("decodeBigEndianInt32sInto throws when output buffer is too small", () => { const bytes = new Uint8Array([0x12, 0x34, 0x56, 0x78]); const out = new Int32Array(0); expect(() => decodeBigEndianInt32sInto(bytes, 0, bytes.length, out)).toThrow(); }); }); ================================================ FILE: ts/src/decoding/bigEndianDecode.ts ================================================ import { bswap32 } from "./fastPforShared"; /** * Decodes big-endian bytes into `out` without allocating the output buffer. * * This function does not copy `bytes`; it writes decoded words into the provided `out` array. * For aligned inputs it may create a temporary typed-array view (`Uint32Array`) over `bytes.buffer` * to speed up decoding. * * If `byteLength` is not a multiple of 4, the final word is padded with zeros. * * @returns Number of int32 words written. * @throws RangeError If `(offset, byteLength)` is out of bounds, or if `out` is too small. */ export function decodeBigEndianInt32sInto( bytes: Uint8Array, offset: number, byteLength: number, out: Uint32Array, ): number { if (offset < 0 || byteLength < 0 || offset + byteLength > bytes.length) { throw new RangeError( `decodeBigEndianInt32sInto: out of bounds (offset=${offset}, byteLength=${byteLength}, bytes.length=${bytes.length})`, ); } const numCompleteInts = Math.floor(byteLength / 4); const hasTrailingBytes = byteLength % 4 !== 0; const numInts = hasTrailingBytes ? numCompleteInts + 1 : numCompleteInts; if (out.length < numInts) { throw new RangeError(`decodeBigEndianInt32sInto: out.length=${out.length} < ${numInts}`); } if (numCompleteInts > 0) { const absoluteOffset = bytes.byteOffset + offset; if ((absoluteOffset & 3) === 0) { const u32 = new Uint32Array(bytes.buffer, absoluteOffset, numCompleteInts); for (let i = 0; i < numCompleteInts; i++) { out[i] = bswap32(u32[i]) | 0; } } else { for (let i = 0; i < numCompleteInts; i++) { const base = offset + i * 4; out[i] = (bytes[base] << 24) | (bytes[base + 1] << 16) | (bytes[base + 2] << 8) | bytes[base + 3] | 0; } } } if (hasTrailingBytes) { const base = offset + numCompleteInts * 4; const remaining = byteLength - numCompleteInts * 4; let v = 0; for (let i = 0; i < remaining; i++) { v |= bytes[base + i] << (24 - i * 8); } out[numCompleteInts] = v | 0; } return numInts; } ================================================ FILE: ts/src/decoding/decodingTestUtils.ts ================================================ import { PhysicalStreamType } from "../metadata/tile/physicalStreamType"; import { LogicalLevelTechnique } from "../metadata/tile/logicalLevelTechnique"; import { PhysicalLevelTechnique } from "../metadata/tile/physicalLevelTechnique"; import { DictionaryType } from "../metadata/tile/dictionaryType"; import { LengthType } from "../metadata/tile/lengthType"; import { OffsetType } from "../metadata/tile/offsetType"; import IntWrapper from "./intWrapper"; import { type Column, type Field, ComplexType, ScalarType } from "../metadata/tileset/tilesetMetadata"; import { encodeBooleanRle, encodeStrings, createStringLengths } from "../encoding/encodingUtils"; import { encodeVarintInt32Value, encodeVarintInt32 } from "../encoding/integerEncodingUtils"; import type { RleEncodedStreamMetadata, StreamMetadata } from "../metadata/tile/streamMetadataDecoder"; import type { LogicalStreamType } from "../metadata/tile/logicalStreamType"; /** * Creates basic stream metadata with logical techniques. */ export function createStreamMetadata( logicalTechnique1: LogicalLevelTechnique, logicalTechnique2: LogicalLevelTechnique = LogicalLevelTechnique.NONE, numValues = 3, ): StreamMetadata { return { physicalStreamType: PhysicalStreamType.DATA, logicalStreamType: { dictionaryType: DictionaryType.NONE }, logicalLevelTechnique1: logicalTechnique1, logicalLevelTechnique2: logicalTechnique2, physicalLevelTechnique: PhysicalLevelTechnique.VARINT, numValues, byteLength: 10, decompressedCount: numValues, }; } /** * Creates RLE-encoded stream metadata. */ export function createRleMetadata( logicalTechnique1: LogicalLevelTechnique, logicalTechnique2: LogicalLevelTechnique, runs: number, numRleValues: number, ): RleEncodedStreamMetadata { return { physicalStreamType: PhysicalStreamType.DATA, logicalStreamType: { dictionaryType: DictionaryType.NONE }, logicalLevelTechnique1: logicalTechnique1, logicalLevelTechnique2: logicalTechnique2, physicalLevelTechnique: PhysicalLevelTechnique.VARINT, numValues: runs * 2, byteLength: 10, decompressedCount: numRleValues, runs, numRleValues, }; } /** * Creates column metadata for STRUCT type columns. */ export function createColumnMetadataForStruct( columnName: string, childFields: Array<{ name: string; type?: number; nullable?: boolean }>, ): Column { const children: Field[] = childFields.map((fieldConfig) => ({ name: fieldConfig.name, nullable: fieldConfig.nullable ?? true, scalarField: { physicalType: fieldConfig.type ?? ScalarType.STRING, type: "physicalType" as const, }, type: "scalarField" as const, })); return { name: columnName, nullable: false, complexType: { physicalType: ComplexType.STRUCT, children, type: "physicalType" as const, }, type: "complexType" as const, }; } /** * Creates a single stream with metadata and data. */ export function createStream( physicalType: PhysicalStreamType, data: Uint8Array, options: { logical?: LogicalStreamType; technique?: PhysicalLevelTechnique; count?: number; } = {}, ): Uint8Array { const count = options.count ?? 0; return buildEncodedStream( { physicalStreamType: physicalType, logicalStreamType: options.logical ?? {}, logicalLevelTechnique1: LogicalLevelTechnique.NONE, logicalLevelTechnique2: LogicalLevelTechnique.NONE, physicalLevelTechnique: options.technique ?? PhysicalLevelTechnique.NONE, numValues: count, byteLength: data.length, decompressedCount: count, }, data, ); } /** * Encodes FSST-compressed strings into a complete stream. * This uses hardcoded test data: ["cat", "dog", "cat"] * @returns Encoded Uint8Array that can be passed to decodeString */ export function encodeFsstStrings(): Uint8Array { const symbolTable = new Uint8Array([99, 97, 116, 100, 111, 103]); // "catdog" const symbolLengths = new Uint32Array([3, 3]); const compressedDictionary = new Uint8Array([0, 1]); const dictionaryLengths = new Uint32Array([3, 3]); const offsets = new Uint32Array([0, 1, 0]); // "cat", "dog", "cat" const numValues = 3; return concatenateBuffers( createStream(PhysicalStreamType.PRESENT, encodeBooleanRle(new Array(numValues).fill(true)), { technique: PhysicalLevelTechnique.VARINT, count: numValues, }), createStream(PhysicalStreamType.DATA, symbolTable, { logical: { dictionaryType: DictionaryType.FSST }, }), createStream(PhysicalStreamType.LENGTH, encodeVarintInt32(symbolLengths), { logical: { lengthType: LengthType.SYMBOL }, technique: PhysicalLevelTechnique.VARINT, count: symbolLengths.length, }), createStream(PhysicalStreamType.OFFSET, encodeVarintInt32(offsets), { logical: { offsetType: OffsetType.STRING }, technique: PhysicalLevelTechnique.VARINT, count: offsets.length, }), createStream(PhysicalStreamType.LENGTH, encodeVarintInt32(dictionaryLengths), { logical: { lengthType: LengthType.DICTIONARY }, technique: PhysicalLevelTechnique.VARINT, count: dictionaryLengths.length, }), createStream(PhysicalStreamType.DATA, compressedDictionary, { logical: { dictionaryType: DictionaryType.SINGLE }, }), ); } /** * Encodes a shared dictionary for struct fields. * @param dictionaryStrings - Array of unique strings in the dictionary * @param options - Encoding options * @returns Object containing length and data streams */ export function encodeSharedDictionary( dictionaryStrings: string[], options: { useFsst?: boolean; dictionaryType?: DictionaryType } = {}, ): { lengthStream: Uint8Array; dataStream: Uint8Array; symbolLengthStream?: Uint8Array; symbolDataStream?: Uint8Array; } { const { useFsst = false, dictionaryType = DictionaryType.SHARED } = options; const encodedDictionary = encodeStrings(dictionaryStrings); const dictionaryLengths = createStringLengths(dictionaryStrings); const lengthStream = createStream( PhysicalStreamType.LENGTH, encodeVarintInt32(new Uint32Array(dictionaryLengths)), { logical: { lengthType: LengthType.DICTIONARY }, technique: PhysicalLevelTechnique.VARINT, count: dictionaryLengths.length, }, ); const dataStream = createStream(PhysicalStreamType.DATA, encodedDictionary, { logical: { dictionaryType: dictionaryType }, count: encodedDictionary.length, }); if (useFsst) { const symbolTable = new Uint8Array([99, 97, 116, 100, 111, 103]); // "catdog" const symbolLengths = new Uint32Array([3, 3]); const symbolLengthStream = createStream(PhysicalStreamType.LENGTH, encodeVarintInt32(symbolLengths), { logical: { lengthType: LengthType.SYMBOL }, technique: PhysicalLevelTechnique.VARINT, count: symbolLengths.length, }); const symbolDataStream = createStream(PhysicalStreamType.DATA, symbolTable, { logical: { dictionaryType: DictionaryType.FSST }, count: symbolTable.length, }); return { lengthStream, dataStream, symbolLengthStream, symbolDataStream }; } return { lengthStream, dataStream }; } /** * Encodes streams for a struct field. * @param offsetIndices - Indices into the shared dictionary * @param presentValues - Boolean array indicating which values are present * @param isPresent - Whether the field itself is present * @returns Encoded streams for the field */ export function encodeStructField(offsetIndices: number[], presentValues: boolean[], isPresent = true): Uint8Array { if (!isPresent) { return encodeNumStreams(0); } const numStreamsEncoded = encodeNumStreams(2); const encodedPresent = createPresentStream(presentValues); const encodedOffsets = createOffsetStream(offsetIndices); return concatenateBuffers(numStreamsEncoded, encodedPresent, encodedOffsets); } function encodeNumStreams(numStreams: number): Uint8Array { const buffer = new Uint8Array(5); const offset = new IntWrapper(0); encodeVarintInt32Value(numStreams, buffer, offset); return buffer.slice(0, offset.get()); } function createPresentStream(presentValues: boolean[]): Uint8Array { const metadata = { physicalStreamType: PhysicalStreamType.PRESENT, logicalStreamType: { dictionaryType: DictionaryType.NONE }, logicalLevelTechnique1: LogicalLevelTechnique.NONE, logicalLevelTechnique2: LogicalLevelTechnique.NONE, physicalLevelTechnique: PhysicalLevelTechnique.VARINT, numValues: presentValues.length, byteLength: 0, decompressedCount: presentValues.length, }; return buildEncodedStream(metadata, encodeBooleanRle(presentValues)); } function createOffsetStream(offsetIndices: number[]): Uint8Array { const metadata = { physicalStreamType: PhysicalStreamType.OFFSET, logicalStreamType: { offsetType: OffsetType.STRING }, logicalLevelTechnique1: LogicalLevelTechnique.NONE, logicalLevelTechnique2: LogicalLevelTechnique.NONE, physicalLevelTechnique: PhysicalLevelTechnique.VARINT, numValues: offsetIndices.length, byteLength: 0, decompressedCount: offsetIndices.length, }; return buildEncodedStream(metadata, encodeVarintInt32(new Uint32Array(offsetIndices))); } /** * Builds a complete encoded stream by combining metadata and data. */ export function buildEncodedStream( streamMetadata: StreamMetadata | RleEncodedStreamMetadata, encodedData: Uint8Array, ): Uint8Array { const updatedMetadata = { ...streamMetadata, byteLength: encodedData.length, }; const metadataBuffer = encodeStreamMetadata(updatedMetadata); const result = new Uint8Array(metadataBuffer.length + encodedData.length); result.set(metadataBuffer, 0); result.set(encodedData, metadataBuffer.length); return result; } /** * Encodes stream metadata into binary format. * - Byte 1: Stream type (physical type in upper 4 bits, logical subtype in lower 4 bits) * - Byte 2: Encodings (llt1[5-7], llt2[2-4], plt[0-1]) * - Varints: numValues, byteLength * - If RLE: Varints: runs, numRleValues */ export function encodeStreamMetadata(metadata: StreamMetadata | RleEncodedStreamMetadata): Uint8Array { const buffer = new Uint8Array(100); let writeOffset = 0; // Byte 1: Stream type buffer[writeOffset++] = encodeStreamTypeByte(metadata); // Byte 2: Encoding techniques buffer[writeOffset++] = encodeEncodingsByte(metadata); // Variable-length fields const offset = new IntWrapper(writeOffset); encodeVarintInt32Value(metadata.numValues, buffer, offset); encodeVarintInt32Value(metadata.byteLength, buffer, offset); // RLE-specific fields if (isRleMetadata(metadata)) { encodeVarintInt32Value(metadata.runs, buffer, offset); encodeVarintInt32Value(metadata.numRleValues, buffer, offset); } return buffer.slice(0, offset.get()); } function encodeStreamTypeByte(metadata: StreamMetadata | RleEncodedStreamMetadata): number { const physicalTypeIndex = Object.values(PhysicalStreamType).indexOf(metadata.physicalStreamType); const lowerNibble = getLogicalSubtypeValue(metadata); return (physicalTypeIndex << 4) | lowerNibble; } function getLogicalSubtypeValue(metadata: StreamMetadata | RleEncodedStreamMetadata): number { const { physicalStreamType, logicalStreamType } = metadata; switch (physicalStreamType) { case PhysicalStreamType.DATA: return logicalStreamType.dictionaryType !== undefined ? Object.values(DictionaryType).indexOf(logicalStreamType.dictionaryType) : 0; case PhysicalStreamType.OFFSET: return logicalStreamType.offsetType !== undefined ? Object.values(OffsetType).indexOf(logicalStreamType.offsetType) : 0; case PhysicalStreamType.LENGTH: return logicalStreamType.lengthType !== undefined ? Object.values(LengthType).indexOf(logicalStreamType.lengthType) : 0; default: return 0; } } function encodeEncodingsByte(metadata: StreamMetadata | RleEncodedStreamMetadata): number { const llt1Index = Object.values(LogicalLevelTechnique).indexOf(metadata.logicalLevelTechnique1); const llt2Index = Object.values(LogicalLevelTechnique).indexOf(metadata.logicalLevelTechnique2); const pltIndex = Object.values(PhysicalLevelTechnique).indexOf(metadata.physicalLevelTechnique); return (llt1Index << 5) | (llt2Index << 2) | pltIndex; } function isRleMetadata(metadata: StreamMetadata | RleEncodedStreamMetadata): metadata is RleEncodedStreamMetadata { return "runs" in metadata && "numRleValues" in metadata; } /** * Concatenates multiple Uint8Array buffers into a single buffer. */ export function concatenateBuffers(...buffers: Uint8Array[]): Uint8Array { const totalLength = buffers.reduce((sum, buf) => sum + buf.length, 0); const result = new Uint8Array(totalLength); let offset = 0; for (const buffer of buffers) { result.set(buffer, offset); offset += buffer.length; } return result; } ================================================ FILE: ts/src/decoding/decodingUtils.spec.ts ================================================ import { describe, expect, it } from "vitest"; import { encodeBooleanRle, encodeByteRle, encodeDoubleLE, encodeFloatsLE, encodeStrings, } from "../encoding/encodingUtils"; import BitVector from "../vector/flat/bitVector"; import { decodeBooleanRle, decodeByteRle, decodeDoublesLE, decodeFloatsLE, decodeString } from "./decodingUtils"; import IntWrapper from "./intWrapper"; describe("decodingUtils", () => { describe("decodeFloatsLE", () => { it("should decode float values from little-endian bytes", () => { const data = new Float32Array([1.5, 2.5]); const encoded = encodeFloatsLE(data); const offset = new IntWrapper(0); const result = decodeFloatsLE(encoded, offset, 2); expect(result).toEqual(data); expect(offset.get()).toBe(8); }); }); describe("decodeDoublesLE", () => { it("should decode double values from little-endian bytes", () => { const data = new Float64Array([Math.PI, Math.E]); const encoded = encodeDoubleLE(data); const offset = new IntWrapper(0); const result = decodeDoublesLE(encoded, offset, 2); expect(result[0]).toBeCloseTo(Math.PI); expect(result[1]).toBeCloseTo(Math.E); expect(offset.get()).toBe(Float64Array.BYTES_PER_ELEMENT * 2); }); }); describe("decodeFloatsLE with nullability", () => { it("should decode nullable float values with nullability buffer", () => { const data = new Float32Array([1.5, 2.5]); const encoded = encodeFloatsLE(data); const offset = new IntWrapper(0); const bitVectorData = new Uint8Array([0b00000101]); const nullabilityBuffer = new BitVector(bitVectorData, 3); const result = decodeFloatsLE(encoded, offset, 2, nullabilityBuffer); expect(result.length).toBe(3); expect(result[0]).toBeCloseTo(1.5); expect(result[1]).toBe(0); expect(result[2]).toBeCloseTo(2.5); }); }); describe("decodeDoublesLE with nullability", () => { it("should decode nullable double values with nullability buffer", () => { const data = new Float32Array([Math.PI, Math.E]); const encoded = encodeDoubleLE(data); const offset = new IntWrapper(0); const bitVectorData = new Uint8Array([0b00000011]); const nullabilityBuffer = new BitVector(bitVectorData, 2); const result = decodeDoublesLE(encoded, offset, 2, nullabilityBuffer); expect(result.length).toBe(2); expect(result[0]).toBeCloseTo(Math.PI); expect(result[1]).toBeCloseTo(Math.E); }); }); describe("decodeBooleanRle", () => { it("should decode boolean RLE", () => { // Create 8 true boolean values const data = [true, true, true, true, true, true, true, true]; const encoded = encodeBooleanRle(data); const offset = new IntWrapper(0); const result = decodeBooleanRle(encoded, 8, encoded.length, offset); // All 8 bits should be set in the first byte expect(result[0]).toBe(0xff); }); }); describe("decodeByteRle", () => { it("should decode byte RLE with runs", () => { // Encode 5 identical bytes const data = new Uint8Array([42, 42, 42, 42, 42]); const encoded = encodeByteRle(data); const offset = new IntWrapper(0); const result = decodeByteRle(encoded, 5, encoded.length, offset); expect(result).toEqual(data); }); it("should decode byte RLE with literals", () => { // Encode 3 different bytes (will be encoded as literals) const data = new Uint8Array([1, 2, 3]); const encoded = encodeByteRle(data); const offset = new IntWrapper(0); const result = decodeByteRle(encoded, 3, encoded.length, offset); expect(result).toEqual(data); expect(offset.get()).toBe(encoded.length); }); it("should handle truncated stream when byteLength runs out before numBytes", () => { // Request 10 bytes but byteLength only allows 2 bytes (header + value) // header=0 means numRuns=3, but stream ends after value byte const data = new Uint8Array([0, 42]); const offset = new IntWrapper(0); const result = decodeByteRle(data, 10, 2, offset); // Should only fill 3 bytes (what the run specified) then stop at stream boundary expect(result.length).toBe(10); expect(result[0]).toBe(42); expect(result[1]).toBe(42); expect(result[2]).toBe(42); // Remaining bytes should be 0 expect(result[3]).toBe(0); expect(result[9]).toBe(0); expect(offset.get()).toBe(2); // Should stop at byteLength boundary }); it("should decode mixed literals and runs", () => { const data = new Uint8Array([1, 2, 5, 5, 5, 5, 5, 7, 8]); const encoded = encodeByteRle(data); const offset = new IntWrapper(0); const result = decodeByteRle(encoded, 9, encoded.length, offset); expect(result).toEqual(data); }); it("should handle 128 literal max", () => { const data = new Uint8Array(130); for (let i = 0; i < 130; i++) { data[i] = i % 256; } const encoded = encodeByteRle(data); const offset = new IntWrapper(0); const result = decodeByteRle(encoded, 130, encoded.length, offset); expect(result).toEqual(data); }); }); describe("decodeString", () => { it("should decode short string", () => { const data = "Hello"; const encoded = encodeStrings([data]); const result = decodeString(encoded, 0, encoded.length); expect(result).toBe(data); }); it("should decode long string", () => { const data = "This is a longer string for testing TextDecoder path"; const encoded = encodeStrings([data]); const result = decodeString(encoded, 0, encoded.length); expect(result).toBe(data); }); it("should handle string with offset", () => { const prefix = "Hello"; const expectedText = "World"; const encoded = encodeStrings([prefix, expectedText]); const prefixLength = new TextEncoder().encode(prefix).length; const result = decodeString(encoded, prefixLength, encoded.length); expect(result).toBe(expectedText); }); }); }); ================================================ FILE: ts/src/decoding/decodingUtils.ts ================================================ import type IntWrapper from "./intWrapper"; import { VectorType } from "../vector/vectorType"; import type BitVector from "../vector/flat/bitVector"; import { decodeStreamMetadata } from "../metadata/tile/streamMetadataDecoder"; import { unpackNullableBoolean, unpackNullable } from "./unpackNullableUtils"; export function skipColumn(numStreams: number, tile: Uint8Array, offset: IntWrapper) { //TODO: add size of column in Mlt for fast skipping for (let i = 0; i < numStreams; i++) { const streamMetadata = decodeStreamMetadata(tile, offset); offset.add(streamMetadata.byteLength); } } export function decodeBooleanRle( buffer: Uint8Array, numBooleans: number, byteLength: number, pos: IntWrapper, nullabilityBuffer?: BitVector, ): Uint8Array { const numBytes = Math.ceil(numBooleans / 8.0); const values = decodeByteRle(buffer, numBytes, byteLength, pos); if (nullabilityBuffer) { return unpackNullableBoolean(values, numBooleans, nullabilityBuffer); } return values; } export function decodeByteRle(buffer: Uint8Array, numBytes: number, byteLength: number, pos: IntWrapper): Uint8Array { const values = new Uint8Array(numBytes); let valueOffset = 0; const streamEndPos = pos.get() + byteLength; while (valueOffset < numBytes) { if (pos.get() >= streamEndPos) { break; } const header = buffer[pos.increment()]; /* Runs */ if (header <= 0x7f) { const numRuns = header + 3; const value = buffer[pos.increment()]; const endValueOffset = Math.min(valueOffset + numRuns, numBytes); values.fill(value, valueOffset, endValueOffset); valueOffset = endValueOffset; } else { /* Literals */ const numLiterals = 256 - header; for (let i = 0; i < numLiterals && valueOffset < numBytes; i++) { values[valueOffset++] = buffer[pos.increment()]; } } } pos.set(streamEndPos); return values; } export function decodeFloatsLE( encodedValues: Uint8Array, pos: IntWrapper, numValues: number, nullabilityBuffer?: BitVector, ): Float32Array { const currentPos = pos.get(); const newOffset = currentPos + numValues * Float32Array.BYTES_PER_ELEMENT; const newBuf = new Uint8Array(encodedValues.subarray(currentPos, newOffset)).buffer; const fb = new Float32Array(newBuf); pos.set(newOffset); if (nullabilityBuffer) { return unpackNullable(fb, nullabilityBuffer, 0); } return fb; } export function decodeDoublesLE( encodedValues: Uint8Array, pos: IntWrapper, numValues: number, nullabilityBuffer?: BitVector, ): Float64Array { const currentPos = pos.get(); const newOffset = currentPos + numValues * Float64Array.BYTES_PER_ELEMENT; const newBuf = new Uint8Array(encodedValues.subarray(currentPos, newOffset)).buffer; const fb = new Float64Array(newBuf); pos.set(newOffset); if (nullabilityBuffer) { return unpackNullable(fb, nullabilityBuffer, 0); } return fb; } const TEXT_DECODER_MIN_LENGTH = 12; const utf8TextDecoder = new TextDecoder(); // Source: https://github.com/mapbox/pbf/issues/106 export function decodeString(buf: Uint8Array, pos: number, end: number): string { if (end - pos >= TEXT_DECODER_MIN_LENGTH) { // longer strings are fast with the built-in browser TextDecoder API return utf8TextDecoder.decode(buf.subarray(pos, end)); } // short strings are fast with custom implementation return readUtf8(buf, pos, end); } function readUtf8(buf, pos, end): string { let str = ""; let i = pos; while (i < end) { const b0 = buf[i]; let c = null; // codepoint let bytesPerSequence = b0 > 0xef ? 4 : b0 > 0xdf ? 3 : b0 > 0xbf ? 2 : 1; if (i + bytesPerSequence > end) break; let b1; let b2; let b3; if (bytesPerSequence === 1) { if (b0 < 0x80) { c = b0; } } else if (bytesPerSequence === 2) { b1 = buf[i + 1]; if ((b1 & 0xc0) === 0x80) { c = ((b0 & 0x1f) << 0x6) | (b1 & 0x3f); if (c <= 0x7f) { c = null; } } } else if (bytesPerSequence === 3) { b1 = buf[i + 1]; b2 = buf[i + 2]; if ((b1 & 0xc0) === 0x80 && (b2 & 0xc0) === 0x80) { c = ((b0 & 0xf) << 0xc) | ((b1 & 0x3f) << 0x6) | (b2 & 0x3f); if (c <= 0x7ff || (c >= 0xd800 && c <= 0xdfff)) { c = null; } } } else if (bytesPerSequence === 4) { b1 = buf[i + 1]; b2 = buf[i + 2]; b3 = buf[i + 3]; if ((b1 & 0xc0) === 0x80 && (b2 & 0xc0) === 0x80 && (b3 & 0xc0) === 0x80) { c = ((b0 & 0xf) << 0x12) | ((b1 & 0x3f) << 0xc) | ((b2 & 0x3f) << 0x6) | (b3 & 0x3f); if (c <= 0xffff || c >= 0x110000) { c = null; } } } if (c === null) { c = 0xfffd; bytesPerSequence = 1; } else if (c > 0xffff) { c -= 0x10000; str += String.fromCharCode(((c >>> 10) & 0x3ff) | 0xd800); c = 0xdc00 | (c & 0x3ff); } str += String.fromCharCode(c); i += bytesPerSequence; } return str; } export function getVectorTypeBooleanStream( numFeatures: number, byteLength: number, data: Uint8Array, offset: IntWrapper, ): VectorType { const valuesPerRun = 0x83; // TODO: use VectorType metadata field for to test which VectorType is used return Math.ceil(numFeatures / valuesPerRun) * 2 === byteLength && /* Test the first value byte if all bits are set to true */ (data[offset.get() + 1] & 0xff) === (bitCount(numFeatures) << 2) - 1 ? VectorType.CONST : VectorType.FLAT; } function bitCount(number): number { //TODO: refactor to get rid of special case handling return number === 0 ? 1 : Math.floor(Math.log2(number) + 1); } ================================================ FILE: ts/src/decoding/fastPforCrossLanguage.spec.ts ================================================ import { describe, expect, it } from "vitest"; import { readdirSync, readFileSync } from "node:fs"; import IntWrapper from "./intWrapper"; import { decodeBigEndianInt32sInto } from "./bigEndianDecode"; import { encodeFastPfor } from "../encoding/integerEncodingUtils"; import { createFastPforWireDecodeWorkspace, decodeFastPfor, decodeFastPforWithWorkspace } from "./integerDecodingUtils"; describe("decodeFastPfor (wire format fixtures)", () => { const FIXTURES_DIR_URL = new URL("../../../test/fixtures/fastpfor/", import.meta.url); function fixtureUrl(fileName: string): URL { return new URL(fileName, FIXTURES_DIR_URL); } function loadFixtureNames(): string[] { return readdirSync(FIXTURES_DIR_URL, { withFileTypes: true }) .filter((entry) => entry.isFile() && entry.name.endsWith("_encoded.bin")) .map((entry) => entry.name.slice(0, -"_encoded.bin".length)) .sort(); } function readEncodedFixtureBytes(name: string): Uint8Array { const buf = readFileSync(fixtureUrl(`${name}_encoded.bin`)); return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); } function readExpectedFixtureValues(name: string): Uint32Array { const buf = readFileSync(fixtureUrl(`${name}_decoded.bin`)); const bytes = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); const out = new Uint32Array(bytes.byteLength >>> 2); decodeBigEndianInt32sInto(bytes, 0, bytes.byteLength, out); return out; } const fixtureNames = loadFixtureNames(); it("has FastPFOR fixtures", () => { expect(fixtureNames.length).toBeGreaterThan(0); }); for (const name of fixtureNames) { describe(name, () => { it("decodes (no workspace)", () => { const encoded = readEncodedFixtureBytes(name); const expectedValues = readExpectedFixtureValues(name); const offset = new IntWrapper(0); const decoded = decodeFastPfor(encoded, expectedValues.length, encoded.length, offset); expect(decoded).toEqual(expectedValues); expect(offset.get()).toBe(encoded.length); }); it("decodes (with workspace reuse)", () => { const encoded = readEncodedFixtureBytes(name); const expectedValues = readExpectedFixtureValues(name); const workspace = createFastPforWireDecodeWorkspace(); const offset1 = new IntWrapper(0); const decoded1 = decodeFastPforWithWorkspace( encoded, expectedValues.length, encoded.length, offset1, workspace, ); expect(decoded1).toEqual(expectedValues); expect(offset1.get()).toBe(encoded.length); const offset2 = new IntWrapper(0); const decoded2 = decodeFastPforWithWorkspace( encoded, expectedValues.length, encoded.length, offset2, workspace, ); expect(decoded2).toEqual(expectedValues); expect(offset2.get()).toBe(encoded.length); }); it("does not depend on input ArrayBuffer alignment (prefix bytes)", () => { const encoded = readEncodedFixtureBytes(name); const expectedValues = readExpectedFixtureValues(name); const prefix = new Uint8Array([0xaa, 0xbb, 0xcc]); const suffix = new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff]); const buffer = new Uint8Array(prefix.length + encoded.length + suffix.length); buffer.set(prefix, 0); buffer.set(encoded, prefix.length); buffer.set(suffix, prefix.length + encoded.length); const offset = new IntWrapper(prefix.length); const decoded = decodeFastPfor(buffer, expectedValues.length, encoded.length, offset); expect(decoded).toEqual(expectedValues); expect(offset.get()).toBe(prefix.length + encoded.length); expect(buffer.subarray(prefix.length + encoded.length)).toEqual(suffix); }); it("round-trips C++ decoded values through TS encode + decode", () => { const values = readExpectedFixtureValues(name); const encoded = encodeFastPfor(values); const offset = new IntWrapper(0); const decoded = decodeFastPfor(encoded, values.length, encoded.length, offset); expect(decoded).toEqual(values); expect(offset.get()).toBe(encoded.length); }); it("matches C++ encoded fixture bytes", () => { const fixtureEncoded = readEncodedFixtureBytes(name); const values = readExpectedFixtureValues(name); const encoded = encodeFastPfor(values); expect(encoded).toEqual(fixtureEncoded); }); }); } }); ================================================ FILE: ts/src/decoding/fastPforDecoder.spec.ts ================================================ import { describe, expect, it } from "vitest"; import { createFastPforEncoderWorkspace, encodeFastPforInt32WithWorkspace } from "../encoding/fastPforEncoder"; import { createDecoderWorkspace, createFastPforWireDecodeWorkspace, decodeFastPforInt32, ensureFastPforWireEncodedWordsCapacity, } from "./fastPforDecoder"; import { BLOCK_SIZE } from "./fastPforShared"; const LARGE_OUTLIER_VALUE = 1_048_576; const DEFAULT_OUTLIER_VALUE = 4; const HEADER_BYTE_MASK = 0xff; const EXCEPTION_COUNT_SHIFT = 8; const MAX_BITS_SHIFT = 16; function createSingleBlockValuesWithExceptionOutliers(outlierValue: number): Uint32Array { const values = new Uint32Array(BLOCK_SIZE); for (let i = 0; i < values.length; i++) values[i] = i % 2; values[10] = outlierValue; values[100] = outlierValue; return values; } function getSinglePageWordLayout(encodedWords: Uint32Array) { const metadataOffsetWordIndex = 1; const metadataOffsetInWords = encodedWords[metadataOffsetWordIndex] | 0; const packedDataEndWordIndex = (metadataOffsetWordIndex + metadataOffsetInWords) | 0; const metadataByteLength = encodedWords[packedDataEndWordIndex] >>> 0; const metadataWordCount = (metadataByteLength + 3) >>> 2; const byteContainerStartWordIndex = (packedDataEndWordIndex + 1) | 0; const exceptionBitmapWordIndex = (byteContainerStartWordIndex + metadataWordCount) | 0; return { packedDataEndWordIndex, byteContainerStartWordIndex, exceptionBitmapWordIndex }; } function parseBlockHeaderWord(headerWord: number): { bitWidth: number; exceptionCount: number; maxBits: number } { return { bitWidth: headerWord & HEADER_BYTE_MASK, exceptionCount: (headerWord >>> EXCEPTION_COUNT_SHIFT) & HEADER_BYTE_MASK, maxBits: (headerWord >>> MAX_BITS_SHIFT) & HEADER_BYTE_MASK, }; } describe("FastPFOR decoder", () => { it("throws on invalid alignedLength (not multiple of 256)", () => { expect(() => decodeFastPforInt32(new Uint32Array([1]), 0)).toThrow(/invalid alignedLength/); }); it("throws when alignedLength exceeds output length", () => { expect(() => decodeFastPforInt32(new Uint32Array([BLOCK_SIZE]), 10)).toThrow(/output buffer too small/); }); it("round-trips empty input", () => { const values = new Uint32Array(0); const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const decoded = decodeFastPforInt32(encoded, values.length); expect(decoded).toEqual(values); }); it("decodes an empty encoded buffer", () => { const decoded = decodeFastPforInt32(new Uint32Array(0), 0); expect(decoded).toEqual(new Uint32Array(0)); }); it("throws when wire decode workspace capacity is negative", () => { expect(() => createFastPforWireDecodeWorkspace(-1)).toThrow(/must be >= 0/); }); it("grows wire decode workspace encodedWords buffer on demand", () => { const workspace = createFastPforWireDecodeWorkspace(1); const initialCapacity = workspace.encodedWords.length; const reused = ensureFastPforWireEncodedWordsCapacity(workspace, initialCapacity); expect(reused).toBe(workspace.encodedWords); const grown = ensureFastPforWireEncodedWordsCapacity(workspace, initialCapacity + 1); expect(grown).toBe(workspace.encodedWords); expect(grown.length).toBeGreaterThan(initialCapacity); }); it("round-trips VByte-only payload (<256 values)", () => { const values = new Uint32Array(100); for (let i = 0; i < values.length; i++) values[i] = i * 7; const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const decoded = decodeFastPforInt32(encoded, values.length); expect(decoded).toEqual(values); }); it("round-trips exactly one aligned block (256 values)", () => { const values = new Uint32Array(BLOCK_SIZE); for (let i = 0; i < values.length; i++) values[i] = i * 31; const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const decoded = decodeFastPforInt32(encoded, values.length); expect(decoded).toEqual(values); }); it("round-trips full-width signed values", () => { const values = new Uint32Array(BLOCK_SIZE); for (let i = 0; i < values.length; i++) values[i] = -(i + 1); const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const decoded = decodeFastPforInt32(encoded, values.length); expect(decoded).toEqual(values); }); it("round-trips representative block bit-widths", () => { const bitWidthCases = [ { bitWidth: 0, samples: [0] }, { bitWidth: 1, samples: [0, 1, 0, 1] }, { bitWidth: 2, samples: [2, 3, 2, 3] }, { bitWidth: 3, samples: [4, 5, 6, 7] }, { bitWidth: 4, samples: [8, 9, 10, 11] }, { bitWidth: 5, samples: [16, 17, 18, 19] }, { bitWidth: 6, samples: [32, 33, 34, 35] }, { bitWidth: 7, samples: [64, 65, 66, 67] }, { bitWidth: 8, samples: [128, 129, 130, 131] }, { bitWidth: 9, samples: [256, 257, 258, 259] }, { bitWidth: 12, samples: [2_048, 2_049, 2_050, 2_051] }, { bitWidth: 13, samples: [4_096, 4_097, 4_098, 4_099] }, { bitWidth: 14, samples: [8_192, 8_193, 8_194, 8_195] }, { bitWidth: 15, samples: [16_384, 16_385, 16_386, 16_387] }, { bitWidth: 16, samples: [32_768, 32_769, 32_770, 32_771] }, { bitWidth: 31, samples: [1_073_741_824, 1_073_741_825, 1_073_741_826, 1_073_741_827] }, ] as const; for (const { bitWidth, samples } of bitWidthCases) { const values = new Uint32Array(BLOCK_SIZE); for (let i = 0; i < values.length; i++) values[i] = samples[i % samples.length]; const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const decoded = decodeFastPforInt32(encoded, values.length); expect(decoded, `round-trip mismatch for bitWidth=${bitWidth}`).toEqual(values); } }); it("round-trips aligned blocks plus VByte tail", () => { const values = new Uint32Array(BLOCK_SIZE * 2 + 3); for (let i = 0; i < values.length; i++) values[i] = i * 31; const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const decoded = decodeFastPforInt32(encoded, values.length); expect(decoded).toEqual(values); }); it("round-trips values with outliers (exceptions path)", () => { const values = new Uint32Array(BLOCK_SIZE * 2); for (let i = 0; i < values.length; i++) values[i] = i % 16; values[10] = 2_147_483_647; values[200] = 1_073_741_824; values[BLOCK_SIZE + 20] = 2_147_483_647; values[BLOCK_SIZE + 210] = 1_073_741_824; const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const decoded = decodeFastPforInt32(encoded, values.length); expect(decoded).toEqual(values); }); it("round-trips exception streams across widths", () => { const exceptionBitWidthCases = [ { exceptionBitWidth: 2, outlierValue: 4 }, { exceptionBitWidth: 3, outlierValue: 8 }, { exceptionBitWidth: 4, outlierValue: 16 }, { exceptionBitWidth: 5, outlierValue: 32 }, { exceptionBitWidth: 6, outlierValue: 64 }, { exceptionBitWidth: 7, outlierValue: 128 }, { exceptionBitWidth: 8, outlierValue: 256 }, { exceptionBitWidth: 9, outlierValue: 512 }, { exceptionBitWidth: 10, outlierValue: 1_024 }, { exceptionBitWidth: 11, outlierValue: 2_048 }, { exceptionBitWidth: 12, outlierValue: 4_096 }, { exceptionBitWidth: 13, outlierValue: 8_192 }, { exceptionBitWidth: 16, outlierValue: 65_536 }, { exceptionBitWidth: 32, outlierValue: -1 }, ] as const; for (const { exceptionBitWidth, outlierValue } of exceptionBitWidthCases) { const values = new Uint32Array(BLOCK_SIZE); if (exceptionBitWidth === 32) { values[0] = outlierValue; } else { for (let i = 0; i < values.length; i++) values[i] = i % 2; values[10] = outlierValue; values[100] = outlierValue; } const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const decoded = decodeFastPforInt32(encoded, values.length); expect(decoded, `round-trip mismatch for exceptionBitWidth=${exceptionBitWidth}`).toEqual(values); } }); it("round-trips exceptionBitWidth=1 fast-path", () => { const values = new Uint32Array(BLOCK_SIZE); for (let i = 0; i < values.length; i++) values[i] = i & 1; values[42] = 2; values[128] = 2; const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const { byteContainerStartWordIndex } = getSinglePageWordLayout(encoded); const headerWord = encoded[byteContainerStartWordIndex] >>> 0; const blockHeader = parseBlockHeaderWord(headerWord); expect(blockHeader.exceptionCount).toBeGreaterThan(0); expect(blockHeader.maxBits).toBe(blockHeader.bitWidth + 1); const decoded = decodeFastPforInt32(encoded, values.length); expect(decoded).toEqual(values); }); }); describe("FastPFOR decoder error cases", () => { function withForcedByteSizeAndNoExceptionStreams(encoded: Uint32Array, forcedByteSize: number): Uint32Array { const corrupted = encoded.slice(); const { packedDataEndWordIndex, byteContainerStartWordIndex } = getSinglePageWordLayout(corrupted); corrupted[packedDataEndWordIndex] = forcedByteSize; const bitmapWordIndex = byteContainerStartWordIndex + ((forcedByteSize + 3) >>> 2); corrupted[bitmapWordIndex] = 0; return corrupted; } it("throws on truncated input (missing page data)", () => { const values = new Uint32Array(BLOCK_SIZE); for (let i = 0; i < values.length; i++) values[i] = i * 31; const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const truncated = encoded.subarray(0, 5); expect(() => decodeFastPforInt32(truncated, values.length)).toThrow(/invalid whereMeta/); }); it("throws on invalid whereMeta in page header", () => { const values = new Uint32Array(BLOCK_SIZE); for (let i = 0; i < values.length; i++) values[i] = i * 3; const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const corruptedEncoded = encoded.slice(); corruptedEncoded[1] = 0; expect(() => decodeFastPforInt32(corruptedEncoded, values.length)).toThrow(/invalid whereMeta/); }); it("throws on invalid block bitWidth in byte container", () => { const values = new Uint32Array(BLOCK_SIZE); for (let i = 0; i < values.length; i++) values[i] = i * 7; const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const { byteContainerStartWordIndex } = getSinglePageWordLayout(encoded); const corruptedEncoded = encoded.slice(); const blockHeaderWord = corruptedEncoded[byteContainerStartWordIndex] >>> 0; corruptedEncoded[byteContainerStartWordIndex] = (blockHeaderWord & 0xffffff00) | 33 | 0; expect(() => decodeFastPforInt32(corruptedEncoded, values.length)).toThrow(/invalid bitWidth/); }); it("throws on packed region mismatch when block metadata is inconsistent", () => { const values = new Uint32Array(BLOCK_SIZE); for (let i = 0; i < values.length; i++) values[i] = i * 31; const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const { byteContainerStartWordIndex } = getSinglePageWordLayout(encoded); const corruptedEncoded = encoded.slice(); const blockHeaderWord = corruptedEncoded[byteContainerStartWordIndex] >>> 0; corruptedEncoded[byteContainerStartWordIndex] = (blockHeaderWord & 0xffffff00) | 0; expect(() => decodeFastPforInt32(corruptedEncoded, values.length)).toThrow(/packed region mismatch/); }); it("throws on invalid maxBits in exception metadata", () => { const values = createSingleBlockValuesWithExceptionOutliers(LARGE_OUTLIER_VALUE); const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const { byteContainerStartWordIndex } = getSinglePageWordLayout(encoded); const corruptedEncoded = encoded.slice(); const blockHeaderWord = corruptedEncoded[byteContainerStartWordIndex] >>> 0; const { bitWidth: blockBitWidth } = parseBlockHeaderWord(blockHeaderWord); const invalidMaxBits = (blockBitWidth - 1) & HEADER_BYTE_MASK; const clearMaxBitsMask = ~(HEADER_BYTE_MASK << MAX_BITS_SHIFT); corruptedEncoded[byteContainerStartWordIndex] = (blockHeaderWord & clearMaxBitsMask) | (invalidMaxBits << MAX_BITS_SHIFT) | 0; expect(() => decodeFastPforInt32(corruptedEncoded, values.length)).toThrow(/invalid maxBits/); }); it("throws on invalid byteSize that moves bitmap out of bounds", () => { const values = new Uint32Array(BLOCK_SIZE); for (let i = 0; i < values.length; i++) values[i] = i; const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const { packedDataEndWordIndex } = getSinglePageWordLayout(encoded); const corruptedEncoded = encoded.slice(); corruptedEncoded[packedDataEndWordIndex] = 0x7fffffff; expect(() => decodeFastPforInt32(corruptedEncoded, values.length)).toThrow(/invalid byteSize/); }); it("throws on truncated exception stream header", () => { const values = new Uint32Array(BLOCK_SIZE); for (let i = 0; i < values.length; i++) values[i] = i * 11; const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const { exceptionBitmapWordIndex } = getSinglePageWordLayout(encoded); const corruptedEncoded = encoded.slice(); corruptedEncoded[exceptionBitmapWordIndex] = corruptedEncoded[exceptionBitmapWordIndex] | (1 << 1); const truncatedEncoded = corruptedEncoded.subarray(0, exceptionBitmapWordIndex + 1); expect(() => decodeFastPforInt32(truncatedEncoded, values.length)).toThrow(/truncated exception stream header/); }); it("throws on truncated exception stream payload", () => { const values = new Uint32Array(BLOCK_SIZE); for (let i = 0; i < values.length; i++) values[i] = i * 13; const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const { exceptionBitmapWordIndex } = getSinglePageWordLayout(encoded); const corruptedEncoded = new Uint32Array(encoded.length + 1); corruptedEncoded.set(encoded); corruptedEncoded[exceptionBitmapWordIndex] = corruptedEncoded[exceptionBitmapWordIndex] | (1 << 1); corruptedEncoded[exceptionBitmapWordIndex + 1] = 1; expect(() => decodeFastPforInt32(corruptedEncoded, values.length)).toThrow(/truncated exception stream/); }); it("throws when byteSize is too small for exception metadata", () => { const smallByteSizeCases = [ { forcedByteSize: 1, expectedError: /byteContainer underflow/ }, { forcedByteSize: 2, expectedError: /exception header underflow/ }, { forcedByteSize: 4, expectedError: /exception positions underflow/ }, ]; for (const { forcedByteSize, expectedError } of smallByteSizeCases) { const values = createSingleBlockValuesWithExceptionOutliers(DEFAULT_OUTLIER_VALUE); const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const corruptedEncoded = withForcedByteSizeAndNoExceptionStreams(encoded, forcedByteSize); expect( () => decodeFastPforInt32(corruptedEncoded, values.length), `forcedByteSize=${forcedByteSize}`, ).toThrow(expectedError); } }); it("throws when maxBits equals bitWidth but exceptions are present", () => { const values = createSingleBlockValuesWithExceptionOutliers(DEFAULT_OUTLIER_VALUE); const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const { byteContainerStartWordIndex } = getSinglePageWordLayout(encoded); const corruptedEncoded = encoded.slice(); const blockHeaderWord = corruptedEncoded[byteContainerStartWordIndex] >>> 0; const { bitWidth: blockBitWidth } = parseBlockHeaderWord(blockHeaderWord); const bytes = new Uint8Array(corruptedEncoded.buffer, corruptedEncoded.byteOffset, corruptedEncoded.byteLength); bytes[byteContainerStartWordIndex * 4 + 2] = blockBitWidth & HEADER_BYTE_MASK; expect(() => decodeFastPforInt32(corruptedEncoded, values.length)).toThrow(/invalid exceptionBitWidth=0/); }); it("throws when exception stream is missing for declared exceptionBitWidth", () => { const values = createSingleBlockValuesWithExceptionOutliers(DEFAULT_OUTLIER_VALUE); const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const { exceptionBitmapWordIndex } = getSinglePageWordLayout(encoded); const corruptedEncoded = encoded.slice(); corruptedEncoded[exceptionBitmapWordIndex] = 0; expect(() => decodeFastPforInt32(corruptedEncoded, values.length)).toThrow(/missing exception stream/); }); it("throws when exception stream pointer overflows stream size", () => { const values = createSingleBlockValuesWithExceptionOutliers(DEFAULT_OUTLIER_VALUE); const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const { exceptionBitmapWordIndex } = getSinglePageWordLayout(encoded); const corruptedEncoded = encoded.slice(); corruptedEncoded[exceptionBitmapWordIndex + 1] = 1; expect(() => decodeFastPforInt32(corruptedEncoded, values.length)).toThrow(/exception stream overflow/); }); it("throws on unterminated VByte value", () => { const encoded = new Uint32Array([0, 0x7f7f7f7f, 0x0000007f]); expect(() => decodeFastPforInt32(encoded, 1)).toThrow(/unterminated value/); }); it("throws when numValues exceeds decoded count", () => { const values = new Uint32Array(100); for (let i = 0; i < values.length; i++) values[i] = i; const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); expect(() => decodeFastPforInt32(encoded, 200)).toThrow(/truncated stream/); }); }); describe("FastPFOR decoder workspace paths", () => { it("reallocates byteContainer when provided workspace is too small", () => { const values = createSingleBlockValuesWithExceptionOutliers(DEFAULT_OUTLIER_VALUE); const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const workspace = createDecoderWorkspace(); workspace.byteContainer = new Uint8Array(1); workspace.byteContainerI32 = undefined; const decoded = decodeFastPforInt32(encoded, values.length, workspace); expect(decoded).toEqual(values); expect(workspace.byteContainer.length).toBeGreaterThan(1); }); it("handles unaligned byteContainer workspace via byte fallback copy path", () => { const values = createSingleBlockValuesWithExceptionOutliers(DEFAULT_OUTLIER_VALUE); const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const workspace = createDecoderWorkspace(); const unalignedByteContainer = new Uint8Array(new ArrayBuffer(2049), 1, 2048); workspace.byteContainer = unalignedByteContainer; workspace.byteContainerI32 = undefined; const decoded = decodeFastPforInt32(encoded, values.length, workspace); expect(decoded).toEqual(values); expect(workspace.byteContainer.byteOffset & 3).toBe(1); }); it("rebuilds aligned int view when workspace has stale int32 view", () => { const values = createSingleBlockValuesWithExceptionOutliers(DEFAULT_OUTLIER_VALUE); const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const workspace = createDecoderWorkspace(); workspace.byteContainer = new Uint8Array(4096); workspace.byteContainerI32 = new Int32Array(4); const decoded = decodeFastPforInt32(encoded, values.length, workspace); expect(decoded).toEqual(values); const byteContainerI32 = workspace.byteContainerI32; expect(byteContainerI32).toBeDefined(); expect(byteContainerI32?.buffer).toBe(workspace.byteContainer.buffer); expect(byteContainerI32?.byteOffset).toBe(workspace.byteContainer.byteOffset); }); }); ================================================ FILE: ts/src/decoding/fastPforDecoder.ts ================================================ import { MASKS, DEFAULT_PAGE_SIZE, BLOCK_SIZE, greatestMultiple, roundUpToMultipleOf32, normalizePageSize, } from "./fastPforShared"; import { fastUnpack32_2, fastUnpack32_3, fastUnpack32_4, fastUnpack32_5, fastUnpack32_6, fastUnpack32_7, fastUnpack32_8, fastUnpack32_9, fastUnpack32_10, fastUnpack32_11, fastUnpack32_12, fastUnpack32_16, fastUnpack256_1, fastUnpack256_2, fastUnpack256_3, fastUnpack256_4, fastUnpack256_5, fastUnpack256_6, fastUnpack256_7, fastUnpack256_8, fastUnpack256_16, fastUnpack256_Generic, } from "./fastPforUnpack"; /** * FastPFOR decoding implementation. * * @remarks * Terminology note: "exceptions" in FastPFOR refer to **outlier values** within a block that do not fit in the * chosen base bit-width for that block. These are stored in separate "exception streams" and later applied back * to the unpacked base values. This is unrelated to JavaScript/TypeScript runtime exceptions. */ /** * Workspace for the FastPFOR decoder. */ export type FastPforDecoderWorkspace = { dataToBePacked: Array; dataPointers: Int32Array; byteContainer: Uint8Array; byteContainerI32?: Int32Array; exceptionSizes: Int32Array; }; /** * Workspace for decoding the FastPFOR *wire format* (big-endian int32 words). * * @remarks * This workspace owns: * - a scratch `encodedWords` buffer to materialize big-endian words * - the reusable `FastPforDecoderWorkspace` used by `decodeFastPforInt32` * * The caller is responsible for creating and reusing this object. */ export type FastPforWireDecodeWorkspace = { encodedWords: Uint32Array; decoderWorkspace: FastPforDecoderWorkspace; }; const MAX_BIT_WIDTH = 32; const BIT_WIDTH_SLOTS = MAX_BIT_WIDTH + 1; const PAGE_SIZE = normalizePageSize(DEFAULT_PAGE_SIZE); const BYTE_CONTAINER_SIZE = ((3 * PAGE_SIZE) / BLOCK_SIZE + PAGE_SIZE) | 0; /** * Creates an isolated workspace for decoding. * Reusing a workspace across calls avoids repeated allocations. */ export function createDecoderWorkspace(): FastPforDecoderWorkspace { const byteContainer = new Uint8Array(BYTE_CONTAINER_SIZE); return { dataToBePacked: new Array(BIT_WIDTH_SLOTS), dataPointers: new Int32Array(BIT_WIDTH_SLOTS), byteContainer, byteContainerI32: new Int32Array( byteContainer.buffer, byteContainer.byteOffset, byteContainer.byteLength >>> 2, ), exceptionSizes: new Int32Array(BIT_WIDTH_SLOTS), }; } export function createFastPforWireDecodeWorkspace( initialEncodedWordCapacity: number = 16, ): FastPforWireDecodeWorkspace { if (initialEncodedWordCapacity < 0) { throw new RangeError(`initialEncodedWordCapacity must be >= 0, got ${initialEncodedWordCapacity}`); } const capacity = Math.max(16, initialEncodedWordCapacity | 0); return { encodedWords: new Uint32Array(capacity), decoderWorkspace: createDecoderWorkspace(), }; } export function ensureFastPforWireEncodedWordsCapacity( workspace: FastPforWireDecodeWorkspace, requiredWordCount: number, ): Uint32Array { if (requiredWordCount <= workspace.encodedWords.length) return workspace.encodedWords; const next = new Uint32Array(Math.max(16, requiredWordCount * 2)); workspace.encodedWords = next; return next; } function materializeByteContainer( inValues: Uint32Array, byteContainerStart: number, byteSize: number, workspace: FastPforDecoderWorkspace, ): Uint8Array { if (workspace.byteContainer.length < byteSize) { workspace.byteContainer = new Uint8Array(byteSize * 2); workspace.byteContainerI32 = undefined; } const byteContainer = workspace.byteContainer; const numFullInts = byteSize >>> 2; if ((byteContainer.byteOffset & 3) === 0) { let intView = workspace.byteContainerI32; if ( !intView || intView.buffer !== byteContainer.buffer || intView.byteOffset !== byteContainer.byteOffset || intView.length < numFullInts ) { intView = workspace.byteContainerI32 = new Int32Array( byteContainer.buffer, byteContainer.byteOffset, byteContainer.byteLength >>> 2, ); } intView.set(inValues.subarray(byteContainerStart, byteContainerStart + numFullInts)); } else { for (let i = 0; i < numFullInts; i = (i + 1) | 0) { const val = inValues[(byteContainerStart + i) | 0] | 0; const base = i << 2; byteContainer[base] = val & 0xff; byteContainer[(base + 1) | 0] = (val >>> 8) & 0xff; byteContainer[(base + 2) | 0] = (val >>> 16) & 0xff; byteContainer[(base + 3) | 0] = (val >>> 24) & 0xff; } } const remainder = byteSize & 3; if (remainder > 0) { const lastIntIdx = (byteContainerStart + numFullInts) | 0; const lastVal = inValues[lastIntIdx] | 0; const base = numFullInts << 2; for (let r = 0; r < remainder; r = (r + 1) | 0) { byteContainer[(base + r) | 0] = (lastVal >>> (r << 3)) & 0xff; } } return byteContainer; } /** * Unpacks the per-bitWidth "exception streams" described by the page's bitmap. * * @remarks * For each bit-width present in the bitmap, a stream header gives the count of outlier values for that * bit-width, followed by packed bits representing those values. * * @param inValues - Packed input (32-bit words). * @param inExcept - Offset (32-bit word index) where the exception bitmap starts. * @param workspace - Decoder workspace used to store the unpacked exception streams. * @returns The new input offset (32-bit word index) after consuming all exception streams. */ function unpackExceptionStreams(inValues: Uint32Array, inExcept: number, workspace: FastPforDecoderWorkspace): number { const bitmap = inValues[inExcept++] | 0; const dataToBePacked = workspace.dataToBePacked; for (let bitWidth = 2; bitWidth <= MAX_BIT_WIDTH; bitWidth = (bitWidth + 1) | 0) { if (((bitmap >>> (bitWidth - 1)) & 1) === 0) continue; if (inExcept >= inValues.length) { throw new Error( `FastPFOR decode: truncated exception stream header (bitWidth=${bitWidth}, streamWordIndex=${inExcept}, needWords=1, availableWords=${inValues.length - inExcept}, encodedWords=${inValues.length})`, ); } const size = inValues[inExcept++] >>> 0; const roundedUp = roundUpToMultipleOf32(size); const wordsNeeded = (size * bitWidth + 31) >>> 5; if (inExcept + wordsNeeded > inValues.length) { throw new Error( `FastPFOR decode: truncated exception stream (bitWidth=${bitWidth}, size=${size}, streamWordIndex=${inExcept}, needWords=${wordsNeeded}, availableWords=${inValues.length - inExcept}, encodedWords=${inValues.length})`, ); } let exceptionStream = dataToBePacked[bitWidth]; if (!exceptionStream || exceptionStream.length < roundedUp) { exceptionStream = dataToBePacked[bitWidth] = new Uint32Array(roundedUp); } let j = 0; for (; j < size; j = (j + 32) | 0) { fastUnpack32(inValues, inExcept, exceptionStream, j, bitWidth); inExcept = (inExcept + bitWidth) | 0; } const overflow = (j - size) | 0; inExcept = (inExcept - ((overflow * bitWidth) >>> 5)) | 0; workspace.exceptionSizes[bitWidth] = size; } return inExcept; } /** * Unpacks one 256-value block from the packed bitstream using a specialized implementation for common widths. * * @param inValues - Packed input (32-bit words). * @param inPos - Input offset (32-bit word index) where the packed block starts. * @param out - Output buffer. * @param outPos - Output offset where the 256 values will be written. * @param bitWidth - Base bit-width used for this block. * @returns The new input offset (32-bit word index) right after the packed block data. */ function unpackBlock256( inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number, bitWidth: number, ): number { switch (bitWidth) { case 1: fastUnpack256_1(inValues, inPos, out, outPos); break; case 2: fastUnpack256_2(inValues, inPos, out, outPos); break; case 3: fastUnpack256_3(inValues, inPos, out, outPos); break; case 4: fastUnpack256_4(inValues, inPos, out, outPos); break; case 5: fastUnpack256_5(inValues, inPos, out, outPos); break; case 6: fastUnpack256_6(inValues, inPos, out, outPos); break; case 7: fastUnpack256_7(inValues, inPos, out, outPos); break; case 8: fastUnpack256_8(inValues, inPos, out, outPos); break; case 16: fastUnpack256_16(inValues, inPos, out, outPos); break; default: fastUnpack256_Generic(inValues, inPos, out, outPos, bitWidth); break; } return (inPos + (bitWidth << 3)) | 0; } /** * Reads and validates the 2-byte block header from the byteContainer. * * @remarks * The header is `[bitWidth, exceptionCount]`, both stored as single bytes. * * @param byteContainer - Byte metadata buffer for the page. * @param byteContainerLen - The valid byte length in `byteContainer` for this page. * @param bytePosIn - Current offset in `byteContainer`. * @param block - Block index within the page (for error messages). * @returns The parsed header and the updated `bytePosIn`. */ function readBlockHeader( byteContainer: Uint8Array, byteContainerLen: number, bytePosIn: number, block: number, ): { bitWidth: number; exceptionCount: number; bytePosIn: number } { if (bytePosIn + 2 > byteContainerLen) { throw new Error( `FastPFOR decode: byteContainer underflow at block=${block} (need 2 bytes for [bitWidth, exceptionCount], bytePos=${bytePosIn}, byteSize=${byteContainerLen})`, ); } const bitWidth = byteContainer[bytePosIn++]; const exceptionCount = byteContainer[bytePosIn++]; if (bitWidth > MAX_BIT_WIDTH) { throw new Error( `FastPFOR decode: invalid bitWidth=${bitWidth} at block=${block} (expected 0..${MAX_BIT_WIDTH}). This likely indicates corrupted or truncated input.`, ); } return { bitWidth, exceptionCount, bytePosIn }; } /** * Reads and validates the exception header for a block. * * @remarks * The header contains `maxBits` (1 byte), which defines the width of the outlier values as * `exceptionBitWidth = maxBits - bitWidth`. * * @param byteContainer - Byte metadata buffer for the page. * @param byteContainerLen - The valid byte length in `byteContainer` for this page. * @param bytePosIn - Current offset in `byteContainer`. * @param bitWidth - Base bit-width for the block. * @param exceptionCount - Number of exceptions/outliers in this block. * @param block - Block index within the page (for error messages). * @returns Parsed `maxBits`, `exceptionBitWidth`, and the updated `bytePosIn`. */ function readBlockExceptionHeader( byteContainer: Uint8Array, byteContainerLen: number, bytePosIn: number, bitWidth: number, exceptionCount: number, block: number, ): { maxBits: number; exceptionBitWidth: number; bytePosIn: number } { if (bytePosIn + 1 > byteContainerLen) { throw new Error( `FastPFOR decode: exception header underflow at block=${block} (need 1 byte for maxBits, bytePos=${bytePosIn}, byteSize=${byteContainerLen})`, ); } const maxBits = byteContainer[bytePosIn++]; if (maxBits < bitWidth || maxBits > MAX_BIT_WIDTH) { throw new Error( `FastPFOR decode: invalid maxBits=${maxBits} at block=${block} (bitWidth=${bitWidth}, expected ${bitWidth}..${MAX_BIT_WIDTH})`, ); } const exceptionBitWidth = (maxBits - bitWidth) | 0; if (exceptionBitWidth < 1 || exceptionBitWidth > MAX_BIT_WIDTH) { throw new Error( `FastPFOR decode: invalid exceptionBitWidth=${exceptionBitWidth} at block=${block} (bitWidth=${bitWidth}, maxBits=${maxBits})`, ); } if (bytePosIn + exceptionCount > byteContainerLen) { throw new Error( `FastPFOR decode: exception positions underflow at block=${block} (need=${exceptionCount}, have=${byteContainerLen - bytePosIn})`, ); } return { maxBits, exceptionBitWidth, bytePosIn }; } /** * Applies (block-local) FastPFOR "exceptions" (outliers) to an already-unpacked base 256-value block. * * @param out - Output buffer containing the base unpacked values for the block. * @param blockOutPos - Offset in `out` where the 256-value block starts. * @param bitWidth - Base bit-width for the block. * @param exceptionCount - Number of exceptions/outliers in this block. * @param byteContainer - Byte metadata buffer for the page. * @param byteContainerLen - The valid byte length in `byteContainer` for this page. * @param bytePosIn - Current offset in `byteContainer` (right after `[bitWidth, exceptionCount]`). * @param workspace - Decoder workspace holding the unpacked exception streams. * @param block - Block index within the page (for error messages). * @returns The updated `bytePosIn` after consuming the exception metadata bytes. * * The exception metadata is stored in `byteContainer`: * - `maxBits` (1 byte): the maximum bit-width of any value in the block * - `exceptionCount` exception positions (1 byte each, 0..255) * * The exception values themselves are read from the pre-unpacked exception streams stored in `workspace`. * Returns the new position in the byteContainer after consuming the exception metadata bytes. */ function applyBlockExceptions( out: Uint32Array, blockOutPos: number, bitWidth: number, exceptionCount: number, byteContainer: Uint8Array, byteContainerLen: number, bytePosIn: number, workspace: FastPforDecoderWorkspace, block: number, ): number { const { maxBits, exceptionBitWidth, bytePosIn: afterHeaderPos, } = readBlockExceptionHeader(byteContainer, byteContainerLen, bytePosIn, bitWidth, exceptionCount, block); bytePosIn = afterHeaderPos; if (exceptionBitWidth === 1) { const shift = 1 << bitWidth; for (let k = 0; k < exceptionCount; k = (k + 1) | 0) { const pos = byteContainer[bytePosIn++]; out[(pos + blockOutPos) | 0] |= shift; } return bytePosIn; } const exceptionValues = workspace.dataToBePacked[exceptionBitWidth]; if (!exceptionValues) { throw new Error( `FastPFOR decode: missing exception stream for exceptionBitWidth=${exceptionBitWidth} (bitWidth=${bitWidth}, maxBits=${maxBits}) at block ${block}`, ); } const exceptionPointers = workspace.dataPointers; let exPtr = exceptionPointers[exceptionBitWidth] | 0; const exSize = workspace.exceptionSizes[exceptionBitWidth] | 0; if (exPtr + exceptionCount > exSize) { throw new Error( `FastPFOR decode: exception stream overflow for exceptionBitWidth=${exceptionBitWidth} (ptr=${exPtr}, need ${exceptionCount}, size=${exSize}) at block ${block}`, ); } for (let k = 0; k < exceptionCount; k = (k + 1) | 0) { const pos = byteContainer[bytePosIn++]; const val = exceptionValues[exPtr++] | 0; out[(pos + blockOutPos) | 0] |= val << bitWidth; } exceptionPointers[exceptionBitWidth] = exPtr; return bytePosIn; } function decodePageBlocks( inValues: Uint32Array, pageStart: number, inPos: number, packedEnd: number, out: Uint32Array, outPos: number, blocks: number, byteContainer: Uint8Array, byteContainerLen: number, workspace: FastPforDecoderWorkspace, ): void { let tmpInPos = inPos | 0; let bytePosIn = 0; for (let run = 0; run < blocks; run = (run + 1) | 0) { const header = readBlockHeader(byteContainer, byteContainerLen, bytePosIn, run); bytePosIn = header.bytePosIn; const bitWidth = header.bitWidth; const exceptionCount = header.exceptionCount; const blockOutPos = (outPos + run * BLOCK_SIZE) | 0; switch (bitWidth) { case 0: out.fill(0, blockOutPos, blockOutPos + BLOCK_SIZE); break; case 32: for (let i = 0; i < BLOCK_SIZE; i = (i + 1) | 0) { out[(blockOutPos + i) | 0] = inValues[(tmpInPos + i) | 0] | 0; } tmpInPos = (tmpInPos + BLOCK_SIZE) | 0; break; default: tmpInPos = unpackBlock256(inValues, tmpInPos, out, blockOutPos, bitWidth); break; } if (exceptionCount > 0) { bytePosIn = applyBlockExceptions( out, blockOutPos, bitWidth, exceptionCount, byteContainer, byteContainerLen, bytePosIn, workspace, run, ); } } if (tmpInPos !== packedEnd) { throw new Error( `FastPFOR decode: packed region mismatch (pageStart=${pageStart}, packedStart=${inPos}, consumedPackedEnd=${tmpInPos}, expectedPackedEnd=${packedEnd}, packedWords=${packedEnd - inPos}, encoded.length=${inValues.length})`, ); } return; } /** * Decodes one FastPFOR page (aligned to 256-value blocks). */ function decodePage( inValues: Uint32Array, out: Uint32Array, inPos: number, outPos: number, thisSize: number, workspace: FastPforDecoderWorkspace, ): number { const pageStart = inPos | 0; const whereMeta = inValues[pageStart] | 0; if (whereMeta <= 0 || pageStart + whereMeta > inValues.length - 1) { throw new Error( `FastPFOR decode: invalid whereMeta=${whereMeta} at pageStart=${pageStart} (expected > 0 and pageStart+whereMeta < encoded.length=${inValues.length})`, ); } const packedStart = (pageStart + 1) | 0; const packedEnd = (pageStart + whereMeta) | 0; const byteSize = inValues[packedEnd] >>> 0; const metaInts = (byteSize + 3) >>> 2; const byteContainerStart = packedEnd + 1; const bitmapPos = byteContainerStart + metaInts; if (bitmapPos >= inValues.length) { throw new Error( `FastPFOR decode: invalid byteSize=${byteSize} (metaInts=${metaInts}, pageStart=${pageStart}, packedEnd=${packedEnd}, byteContainerStart=${byteContainerStart}) causes bitmapPos=${bitmapPos} out of bounds (encoded.length=${inValues.length})`, ); } const byteContainer = materializeByteContainer(inValues, byteContainerStart, byteSize, workspace); const byteContainerLen = byteSize; const inExcept = unpackExceptionStreams(inValues, bitmapPos, workspace); const exceptionPointers = workspace.dataPointers; exceptionPointers.fill(0); const startOutPos = outPos | 0; const blocks = (thisSize / BLOCK_SIZE) | 0; decodePageBlocks( inValues, pageStart, packedStart, packedEnd, out, startOutPos, blocks, byteContainer, byteContainerLen, workspace, ); return inExcept; } function decodeAlignedPages( inValues: Uint32Array, out: Uint32Array, inPos: number, outPos: number, outLength: number, workspace: FastPforDecoderWorkspace, ): number { const alignedOutLength = greatestMultiple(outLength, BLOCK_SIZE); const finalOut = outPos + alignedOutLength; let tmpOutPos = outPos; let tmpInPos = inPos; while (tmpOutPos !== finalOut) { const thisSize = Math.min(PAGE_SIZE, finalOut - tmpOutPos); tmpInPos = decodePage(inValues, out, tmpInPos, tmpOutPos, thisSize, workspace); tmpOutPos = (tmpOutPos + thisSize) | 0; } return tmpInPos; } /** * Decodes the VariableByte tail (MSB=1 terminator, opposite of Protobuf Varint). */ function decodeVByte( inValues: Uint32Array, inPos: number, inLength: number, out: Uint32Array, outPos: number, expectedCount: number, ): number { if (expectedCount === 0) return inPos; let bitOffset = 0; let wordIndex = inPos; const finalWordIndex = inPos + inLength; const outPos0 = outPos; let tmpOutPos = outPos; const targetOut = outPos + expectedCount; let accumulator = 0; let accumulatorShift = 0; while (wordIndex < finalWordIndex && tmpOutPos < targetOut) { const word = inValues[wordIndex]; const byte = (word >>> bitOffset) & 0xff; bitOffset += 8; wordIndex += bitOffset >>> 5; bitOffset &= 31; accumulator |= (byte & 0x7f) << accumulatorShift; if ((byte & 0x80) !== 0) { out[tmpOutPos++] = accumulator | 0; accumulator = 0; accumulatorShift = 0; } else { accumulatorShift += 7; if (accumulatorShift > 28) { throw new Error( `FastPFOR VByte: unterminated value (expected MSB=1 terminator within 5 bytes; shift=${accumulatorShift}, partial=${accumulator}, decoded=${tmpOutPos - outPos0}/${expectedCount}, inPos=${wordIndex}, inEnd=${finalWordIndex})`, ); } } } if (tmpOutPos !== targetOut) { throw new Error( `FastPFOR VByte: truncated stream (decoded=${tmpOutPos - outPos0}, expected=${expectedCount}, consumedWords=${wordIndex - inPos}/${inLength}, vbyteStart=${inPos}, vbyteEnd=${finalWordIndex})`, ); } return wordIndex; } /** * Decodes a sequence of FastPFOR-encoded integers. * * @param encoded The input buffer containing FastPFOR encoded data. * @param numValues The number of integers expected to be decoded. * @param workspace Optional workspace for reuse across calls. If omitted, a new workspace is created per call. */ export function decodeFastPforInt32( encoded: Uint32Array, numValues: number, workspace?: FastPforDecoderWorkspace, ): Uint32Array { let inPos = 0; let outPos = 0; const decoded = new Uint32Array(numValues); const decoderWorkspace = workspace ?? createDecoderWorkspace(); if (encoded.length > 0) { const alignedLength = encoded[inPos] | 0; inPos = (inPos + 1) | 0; if ((alignedLength & (BLOCK_SIZE - 1)) !== 0) { throw new Error( `FastPFOR decode: invalid alignedLength=${alignedLength} (expected multiple of ${BLOCK_SIZE})`, ); } if (outPos + alignedLength > decoded.length) { throw new Error( `FastPFOR decode: output buffer too small (outPos=${outPos}, alignedLength=${alignedLength}, out.length=${decoded.length})`, ); } inPos = decodeAlignedPages(encoded, decoded, inPos, outPos, alignedLength, decoderWorkspace); outPos = (outPos + alignedLength) | 0; } const remainingLength = (encoded.length - inPos) | 0; const expectedTail = (numValues - outPos) | 0; decodeVByte(encoded, inPos, remainingLength, decoded, outPos, expectedTail); return decoded; } function fastUnpack32(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number, bitWidth: number): void { switch (bitWidth) { case 2: fastUnpack32_2(inValues, inPos, out, outPos); return; case 3: fastUnpack32_3(inValues, inPos, out, outPos); return; case 4: fastUnpack32_4(inValues, inPos, out, outPos); return; case 5: fastUnpack32_5(inValues, inPos, out, outPos); return; case 6: fastUnpack32_6(inValues, inPos, out, outPos); return; case 7: fastUnpack32_7(inValues, inPos, out, outPos); return; case 8: fastUnpack32_8(inValues, inPos, out, outPos); return; case 9: fastUnpack32_9(inValues, inPos, out, outPos); return; case 10: fastUnpack32_10(inValues, inPos, out, outPos); return; case 11: fastUnpack32_11(inValues, inPos, out, outPos); return; case 12: fastUnpack32_12(inValues, inPos, out, outPos); return; case 16: fastUnpack32_16(inValues, inPos, out, outPos); return; case 32: for (let i = 0; i < 32; i = (i + 1) | 0) { out[(outPos + i) | 0] = inValues[(inPos + i) | 0] | 0; } return; default: break; } const valueMask = MASKS[bitWidth] >>> 0; let inputWordIndex = inPos; let bitOffset = 0; let currentWord = inValues[inputWordIndex] >>> 0; for (let i = 0; i < 32; i++) { if (bitOffset + bitWidth <= 32) { const value = (currentWord >>> bitOffset) & valueMask; out[outPos + i] = value | 0; bitOffset += bitWidth; if (bitOffset === 32) { bitOffset = 0; inputWordIndex++; if (i !== 31) currentWord = inValues[inputWordIndex] >>> 0; } } else { const lowBits = 32 - bitOffset; const low = currentWord >>> bitOffset; inputWordIndex++; currentWord = inValues[inputWordIndex] >>> 0; const highMask = MASKS[bitWidth - lowBits] >>> 0; const high = currentWord & highMask; const value = (low | (high << lowBits)) & valueMask; out[outPos + i] = value | 0; bitOffset = bitWidth - lowBits; } } } ================================================ FILE: ts/src/decoding/fastPforShared.spec.ts ================================================ import { describe, expect, it } from "vitest"; import { BLOCK_SIZE, DEFAULT_PAGE_SIZE, bswap32, greatestMultiple, normalizePageSize, roundUpToMultipleOf32, } from "./fastPforShared"; describe("FastPforShared", () => { describe("endian helpers", () => { it("bswap32 swaps bytes", () => { expect(bswap32(0x11223344)).toBe(0x44332211); expect(bswap32(0x00000000)).toBe(0x00000000); expect(bswap32(0xffffffff)).toBe(0xffffffff); expect(bswap32(0x89abcdef)).toBe(0xefcdab89); }); }); describe("normalizePageSize", () => { it("returns DEFAULT_PAGE_SIZE for invalid inputs", () => { expect(normalizePageSize(0)).toBe(DEFAULT_PAGE_SIZE); expect(normalizePageSize(-1)).toBe(DEFAULT_PAGE_SIZE); expect(normalizePageSize(Number.NaN)).toBe(DEFAULT_PAGE_SIZE); expect(normalizePageSize(Number.POSITIVE_INFINITY)).toBe(DEFAULT_PAGE_SIZE); expect(normalizePageSize(Number.NEGATIVE_INFINITY)).toBe(DEFAULT_PAGE_SIZE); }); it("rounds down to nearest multiple of BLOCK_SIZE", () => { expect(normalizePageSize(BLOCK_SIZE * 2 + 10)).toBe(BLOCK_SIZE * 2); expect(normalizePageSize(BLOCK_SIZE * 10 + BLOCK_SIZE - 1)).toBe(BLOCK_SIZE * 10); }); it("clamps small values to BLOCK_SIZE (min size)", () => { expect(normalizePageSize(1)).toBe(BLOCK_SIZE); expect(normalizePageSize(BLOCK_SIZE - 1)).toBe(BLOCK_SIZE); expect(normalizePageSize(BLOCK_SIZE)).toBe(BLOCK_SIZE); }); it("handles float inputs by flooring", () => { expect(normalizePageSize(BLOCK_SIZE * 2.5)).toBe(BLOCK_SIZE * 2); }); it("returns input when already a valid multiple of BLOCK_SIZE", () => { expect(normalizePageSize(BLOCK_SIZE * 4)).toBe(BLOCK_SIZE * 4); }); it("handles large values", () => { expect(normalizePageSize(BLOCK_SIZE * 1_000)).toBe(BLOCK_SIZE * 1_000); expect(normalizePageSize(BLOCK_SIZE * 1_000 + 123)).toBe(BLOCK_SIZE * 1_000); }); it("returns DEFAULT_PAGE_SIZE for non-number inputs", () => { expect(normalizePageSize(undefined as any)).toBe(DEFAULT_PAGE_SIZE); expect(normalizePageSize(null as any)).toBe(DEFAULT_PAGE_SIZE); }); }); describe("greatestMultiple", () => { it("rounds down to the nearest multiple", () => { expect(greatestMultiple(10, 3)).toBe(9); expect(greatestMultiple(12, 3)).toBe(12); }); }); describe("roundUpToMultipleOf32", () => { it("rounds up to a multiple of 32", () => { expect(roundUpToMultipleOf32(0)).toBe(0); expect(roundUpToMultipleOf32(1)).toBe(32); expect(roundUpToMultipleOf32(32)).toBe(32); expect(roundUpToMultipleOf32(33)).toBe(64); }); }); }); ================================================ FILE: ts/src/decoding/fastPforShared.ts ================================================ /** * Bit masks for each bitwidth 0-32. * DO NOT MUTATE - this is a shared constant. */ const masks = new Uint32Array(33); masks[0] = 0; for (let bitWidth = 1; bitWidth <= 32; bitWidth++) { masks[bitWidth] = bitWidth === 32 ? 0xffffffff : 0xffffffff >>> (32 - bitWidth); } export const MASKS: Readonly = masks; export const DEFAULT_PAGE_SIZE = 65536; export const BLOCK_SIZE = 256; export function greatestMultiple(value: number, factor: number): number { return value - (value % factor); } export function roundUpToMultipleOf32(value: number): number { return greatestMultiple(value + 31, 32); } export function normalizePageSize(pageSize: number): number { if (!Number.isFinite(pageSize) || pageSize <= 0) return DEFAULT_PAGE_SIZE; const aligned = greatestMultiple(Math.floor(pageSize), BLOCK_SIZE); return aligned === 0 ? BLOCK_SIZE : aligned; } export function bswap32(value: number): number { const x = value >>> 0; return (((x & 0xff) << 24) | ((x & 0xff00) << 8) | ((x >>> 8) & 0xff00) | ((x >>> 24) & 0xff)) >>> 0; } ================================================ FILE: ts/src/decoding/fastPforUnpack.spec.ts ================================================ import { describe, expect, it } from "vitest"; import { fastPack32 } from "../encoding/fastPforEncoder"; import { MASKS } from "./fastPforShared"; import { fastUnpack32_1, fastUnpack32_2, fastUnpack32_3, fastUnpack32_4, fastUnpack32_5, fastUnpack32_6, fastUnpack32_7, fastUnpack32_8, fastUnpack32_9, fastUnpack32_10, fastUnpack32_11, fastUnpack32_12, fastUnpack32_16, fastUnpack256_1, fastUnpack256_2, fastUnpack256_3, fastUnpack256_4, fastUnpack256_5, fastUnpack256_6, fastUnpack256_7, fastUnpack256_8, fastUnpack256_16, fastUnpack256_Generic, } from "./fastPforUnpack"; describe("FastPFOR Unpack Library", () => { type Unpacker = (inValues: Int32Array, inPos: number, out: Int32Array, outPos: number) => void; const UNPACK32_TEST_CASES: Array<{ bitWidth: number; unpacker: Unpacker }> = [ { bitWidth: 1, unpacker: fastUnpack32_1 }, { bitWidth: 2, unpacker: fastUnpack32_2 }, { bitWidth: 3, unpacker: fastUnpack32_3 }, { bitWidth: 4, unpacker: fastUnpack32_4 }, { bitWidth: 5, unpacker: fastUnpack32_5 }, { bitWidth: 6, unpacker: fastUnpack32_6 }, { bitWidth: 7, unpacker: fastUnpack32_7 }, { bitWidth: 8, unpacker: fastUnpack32_8 }, { bitWidth: 9, unpacker: fastUnpack32_9 }, { bitWidth: 10, unpacker: fastUnpack32_10 }, { bitWidth: 11, unpacker: fastUnpack32_11 }, { bitWidth: 12, unpacker: fastUnpack32_12 }, { bitWidth: 16, unpacker: fastUnpack32_16 }, ]; const UNPACK256_SPECIALIZED_TEST_CASES: Array<{ bitWidth: number; unpacker: Unpacker }> = [ { bitWidth: 1, unpacker: fastUnpack256_1 }, { bitWidth: 2, unpacker: fastUnpack256_2 }, { bitWidth: 3, unpacker: fastUnpack256_3 }, { bitWidth: 4, unpacker: fastUnpack256_4 }, { bitWidth: 5, unpacker: fastUnpack256_5 }, { bitWidth: 6, unpacker: fastUnpack256_6 }, { bitWidth: 7, unpacker: fastUnpack256_7 }, { bitWidth: 8, unpacker: fastUnpack256_8 }, { bitWidth: 16, unpacker: fastUnpack256_16 }, ]; const UNPACK256_GENERIC_BIT_WIDTHS: number[] = [9, 10, 11, 12, 13, 14, 15]; function pack256(values: Int32Array, bitWidth: number): Int32Array { const out = new Int32Array(bitWidth * 8); for (let chunk = 0; chunk < 8; chunk++) { fastPack32(values, chunk * 32, out, chunk * bitWidth, bitWidth); } return out; } function makeRamp(length: number, valueMask: number): Int32Array { const values = new Int32Array(length); for (let i = 0; i < length; i++) values[i] = i & valueMask; return values; } function makeMaxPattern(length: number, valueMask: number): Int32Array { return new Int32Array(length).fill(valueMask); } for (const { bitWidth, unpacker } of UNPACK32_TEST_CASES) { describe(`fastUnpack32_${bitWidth}`, () => { it("round-trips ramp", () => { const valueMask = MASKS[bitWidth] | 0; const expected = makeRamp(32, valueMask); const input = new Int32Array(bitWidth); fastPack32(expected, 0, input, 0, bitWidth); const out = new Int32Array(32); unpacker(input, 0, out, 0); expect(out).toEqual(expected); }); it("round-trips max", () => { const valueMask = MASKS[bitWidth] | 0; const expected = makeMaxPattern(32, valueMask); const input = new Int32Array(bitWidth); fastPack32(expected, 0, input, 0, bitWidth); const out = new Int32Array(32); unpacker(input, 0, out, 0); expect(out).toEqual(expected); }); }); } for (const { bitWidth, unpacker } of UNPACK256_SPECIALIZED_TEST_CASES) { describe(`fastUnpack256_${bitWidth}`, () => { it("round-trips ramp", () => { const valueMask = MASKS[bitWidth] | 0; const expected = makeRamp(256, valueMask); const input = pack256(expected, bitWidth); const out = new Int32Array(256); unpacker(input, 0, out, 0); expect(out).toEqual(expected); }); it("round-trips max", () => { const valueMask = MASKS[bitWidth] | 0; const expected = makeMaxPattern(256, valueMask); const input = pack256(expected, bitWidth); const out = new Int32Array(256); unpacker(input, 0, out, 0); expect(out).toEqual(expected); }); it("matches fastUnpack256_Generic", () => { const valueMask = MASKS[bitWidth] | 0; const expected = makeRamp(256, valueMask); const input = pack256(expected, bitWidth); const outSpecific = new Int32Array(256); const outGeneric = new Int32Array(256); unpacker(input, 0, outSpecific, 0); fastUnpack256_Generic(input, 0, outGeneric, 0, bitWidth); expect(outSpecific).toEqual(outGeneric); }); }); } for (const bitWidth of UNPACK256_GENERIC_BIT_WIDTHS) { describe(`fastUnpack256_Generic bitWidth=${bitWidth}`, () => { it("round-trips ramp", () => { const valueMask = MASKS[bitWidth] | 0; const expected = makeRamp(256, valueMask); const input = pack256(expected, bitWidth); const out = new Int32Array(256); fastUnpack256_Generic(input, 0, out, 0, bitWidth); expect(out).toEqual(expected); }); it("round-trips max", () => { const valueMask = MASKS[bitWidth] | 0; const expected = makeMaxPattern(256, valueMask); const input = pack256(expected, bitWidth); const out = new Int32Array(256); fastUnpack256_Generic(input, 0, out, 0, bitWidth); expect(out).toEqual(expected); }); }); } }); ================================================ FILE: ts/src/decoding/fastPforUnpack.ts ================================================ import { MASKS } from "./fastPforShared"; export function fastUnpack32_1(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { const in0 = inValues[inPos] >>> 0; for (let i = 0; i < 32; i++) { out[outPos + i] = (in0 >>> i) & 1; } } export function fastUnpack32_2(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; const in0 = inValues[inPos] >>> 0; const in1 = inValues[inPos + 1] >>> 0; out[op++] = (in0 >>> 0) & 0x3; out[op++] = (in0 >>> 2) & 0x3; out[op++] = (in0 >>> 4) & 0x3; out[op++] = (in0 >>> 6) & 0x3; out[op++] = (in0 >>> 8) & 0x3; out[op++] = (in0 >>> 10) & 0x3; out[op++] = (in0 >>> 12) & 0x3; out[op++] = (in0 >>> 14) & 0x3; out[op++] = (in0 >>> 16) & 0x3; out[op++] = (in0 >>> 18) & 0x3; out[op++] = (in0 >>> 20) & 0x3; out[op++] = (in0 >>> 22) & 0x3; out[op++] = (in0 >>> 24) & 0x3; out[op++] = (in0 >>> 26) & 0x3; out[op++] = (in0 >>> 28) & 0x3; out[op++] = (in0 >>> 30) & 0x3; out[op++] = (in1 >>> 0) & 0x3; out[op++] = (in1 >>> 2) & 0x3; out[op++] = (in1 >>> 4) & 0x3; out[op++] = (in1 >>> 6) & 0x3; out[op++] = (in1 >>> 8) & 0x3; out[op++] = (in1 >>> 10) & 0x3; out[op++] = (in1 >>> 12) & 0x3; out[op++] = (in1 >>> 14) & 0x3; out[op++] = (in1 >>> 16) & 0x3; out[op++] = (in1 >>> 18) & 0x3; out[op++] = (in1 >>> 20) & 0x3; out[op++] = (in1 >>> 22) & 0x3; out[op++] = (in1 >>> 24) & 0x3; out[op++] = (in1 >>> 26) & 0x3; out[op++] = (in1 >>> 28) & 0x3; out[op] = (in1 >>> 30) & 0x3; } export function fastUnpack32_3(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; const in0 = inValues[inPos] >>> 0; const in1 = inValues[inPos + 1] >>> 0; const in2 = inValues[inPos + 2] >>> 0; out[op++] = (in0 >>> 0) & 0x7; out[op++] = (in0 >>> 3) & 0x7; out[op++] = (in0 >>> 6) & 0x7; out[op++] = (in0 >>> 9) & 0x7; out[op++] = (in0 >>> 12) & 0x7; out[op++] = (in0 >>> 15) & 0x7; out[op++] = (in0 >>> 18) & 0x7; out[op++] = (in0 >>> 21) & 0x7; out[op++] = (in0 >>> 24) & 0x7; out[op++] = (in0 >>> 27) & 0x7; out[op++] = ((in0 >>> 30) | ((in1 & 0x1) << 2)) & 0x7; out[op++] = (in1 >>> 1) & 0x7; out[op++] = (in1 >>> 4) & 0x7; out[op++] = (in1 >>> 7) & 0x7; out[op++] = (in1 >>> 10) & 0x7; out[op++] = (in1 >>> 13) & 0x7; out[op++] = (in1 >>> 16) & 0x7; out[op++] = (in1 >>> 19) & 0x7; out[op++] = (in1 >>> 22) & 0x7; out[op++] = (in1 >>> 25) & 0x7; out[op++] = (in1 >>> 28) & 0x7; out[op++] = ((in1 >>> 31) | ((in2 & 0x3) << 1)) & 0x7; out[op++] = (in2 >>> 2) & 0x7; out[op++] = (in2 >>> 5) & 0x7; out[op++] = (in2 >>> 8) & 0x7; out[op++] = (in2 >>> 11) & 0x7; out[op++] = (in2 >>> 14) & 0x7; out[op++] = (in2 >>> 17) & 0x7; out[op++] = (in2 >>> 20) & 0x7; out[op++] = (in2 >>> 23) & 0x7; out[op++] = (in2 >>> 26) & 0x7; out[op] = (in2 >>> 29) & 0x7; } export function fastUnpack32_4(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; const in0 = inValues[inPos] >>> 0; const in1 = inValues[inPos + 1] >>> 0; const in2 = inValues[inPos + 2] >>> 0; const in3 = inValues[inPos + 3] >>> 0; out[op++] = (in0 >>> 0) & 0xf; out[op++] = (in0 >>> 4) & 0xf; out[op++] = (in0 >>> 8) & 0xf; out[op++] = (in0 >>> 12) & 0xf; out[op++] = (in0 >>> 16) & 0xf; out[op++] = (in0 >>> 20) & 0xf; out[op++] = (in0 >>> 24) & 0xf; out[op++] = (in0 >>> 28) & 0xf; out[op++] = (in1 >>> 0) & 0xf; out[op++] = (in1 >>> 4) & 0xf; out[op++] = (in1 >>> 8) & 0xf; out[op++] = (in1 >>> 12) & 0xf; out[op++] = (in1 >>> 16) & 0xf; out[op++] = (in1 >>> 20) & 0xf; out[op++] = (in1 >>> 24) & 0xf; out[op++] = (in1 >>> 28) & 0xf; out[op++] = (in2 >>> 0) & 0xf; out[op++] = (in2 >>> 4) & 0xf; out[op++] = (in2 >>> 8) & 0xf; out[op++] = (in2 >>> 12) & 0xf; out[op++] = (in2 >>> 16) & 0xf; out[op++] = (in2 >>> 20) & 0xf; out[op++] = (in2 >>> 24) & 0xf; out[op++] = (in2 >>> 28) & 0xf; out[op++] = (in3 >>> 0) & 0xf; out[op++] = (in3 >>> 4) & 0xf; out[op++] = (in3 >>> 8) & 0xf; out[op++] = (in3 >>> 12) & 0xf; out[op++] = (in3 >>> 16) & 0xf; out[op++] = (in3 >>> 20) & 0xf; out[op++] = (in3 >>> 24) & 0xf; out[op] = (in3 >>> 28) & 0xf; } export function fastUnpack32_5(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; const in0 = inValues[inPos] >>> 0; const in1 = inValues[inPos + 1] >>> 0; const in2 = inValues[inPos + 2] >>> 0; const in3 = inValues[inPos + 3] >>> 0; const in4 = inValues[inPos + 4] >>> 0; out[op++] = (in0 >>> 0) & 0x1f; out[op++] = (in0 >>> 5) & 0x1f; out[op++] = (in0 >>> 10) & 0x1f; out[op++] = (in0 >>> 15) & 0x1f; out[op++] = (in0 >>> 20) & 0x1f; out[op++] = (in0 >>> 25) & 0x1f; out[op++] = ((in0 >>> 30) | ((in1 & 0x7) << 2)) & 0x1f; out[op++] = (in1 >>> 3) & 0x1f; out[op++] = (in1 >>> 8) & 0x1f; out[op++] = (in1 >>> 13) & 0x1f; out[op++] = (in1 >>> 18) & 0x1f; out[op++] = (in1 >>> 23) & 0x1f; out[op++] = ((in1 >>> 28) | ((in2 & 0x1) << 4)) & 0x1f; out[op++] = (in2 >>> 1) & 0x1f; out[op++] = (in2 >>> 6) & 0x1f; out[op++] = (in2 >>> 11) & 0x1f; out[op++] = (in2 >>> 16) & 0x1f; out[op++] = (in2 >>> 21) & 0x1f; out[op++] = (in2 >>> 26) & 0x1f; out[op++] = ((in2 >>> 31) | ((in3 & 0xf) << 1)) & 0x1f; out[op++] = (in3 >>> 4) & 0x1f; out[op++] = (in3 >>> 9) & 0x1f; out[op++] = (in3 >>> 14) & 0x1f; out[op++] = (in3 >>> 19) & 0x1f; out[op++] = (in3 >>> 24) & 0x1f; out[op++] = ((in3 >>> 29) | ((in4 & 0x3) << 3)) & 0x1f; out[op++] = (in4 >>> 2) & 0x1f; out[op++] = (in4 >>> 7) & 0x1f; out[op++] = (in4 >>> 12) & 0x1f; out[op++] = (in4 >>> 17) & 0x1f; out[op++] = (in4 >>> 22) & 0x1f; out[op] = (in4 >>> 27) & 0x1f; } export function fastUnpack32_6(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; const in0 = inValues[inPos] >>> 0; const in1 = inValues[inPos + 1] >>> 0; const in2 = inValues[inPos + 2] >>> 0; const in3 = inValues[inPos + 3] >>> 0; const in4 = inValues[inPos + 4] >>> 0; const in5 = inValues[inPos + 5] >>> 0; out[op++] = (in0 >>> 0) & 0x3f; out[op++] = (in0 >>> 6) & 0x3f; out[op++] = (in0 >>> 12) & 0x3f; out[op++] = (in0 >>> 18) & 0x3f; out[op++] = (in0 >>> 24) & 0x3f; out[op++] = ((in0 >>> 30) | ((in1 & 0xf) << 2)) & 0x3f; out[op++] = (in1 >>> 4) & 0x3f; out[op++] = (in1 >>> 10) & 0x3f; out[op++] = (in1 >>> 16) & 0x3f; out[op++] = (in1 >>> 22) & 0x3f; out[op++] = ((in1 >>> 28) | ((in2 & 0x3) << 4)) & 0x3f; out[op++] = (in2 >>> 2) & 0x3f; out[op++] = (in2 >>> 8) & 0x3f; out[op++] = (in2 >>> 14) & 0x3f; out[op++] = (in2 >>> 20) & 0x3f; out[op++] = (in2 >>> 26) & 0x3f; out[op++] = (in3 >>> 0) & 0x3f; out[op++] = (in3 >>> 6) & 0x3f; out[op++] = (in3 >>> 12) & 0x3f; out[op++] = (in3 >>> 18) & 0x3f; out[op++] = (in3 >>> 24) & 0x3f; out[op++] = ((in3 >>> 30) | ((in4 & 0xf) << 2)) & 0x3f; out[op++] = (in4 >>> 4) & 0x3f; out[op++] = (in4 >>> 10) & 0x3f; out[op++] = (in4 >>> 16) & 0x3f; out[op++] = (in4 >>> 22) & 0x3f; out[op++] = ((in4 >>> 28) | ((in5 & 0x3) << 4)) & 0x3f; out[op++] = (in5 >>> 2) & 0x3f; out[op++] = (in5 >>> 8) & 0x3f; out[op++] = (in5 >>> 14) & 0x3f; out[op++] = (in5 >>> 20) & 0x3f; out[op] = (in5 >>> 26) & 0x3f; } export function fastUnpack32_7(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; const in0 = inValues[inPos] >>> 0; const in1 = inValues[inPos + 1] >>> 0; const in2 = inValues[inPos + 2] >>> 0; const in3 = inValues[inPos + 3] >>> 0; const in4 = inValues[inPos + 4] >>> 0; const in5 = inValues[inPos + 5] >>> 0; const in6 = inValues[inPos + 6] >>> 0; out[op++] = (in0 >>> 0) & 0x7f; out[op++] = (in0 >>> 7) & 0x7f; out[op++] = (in0 >>> 14) & 0x7f; out[op++] = (in0 >>> 21) & 0x7f; out[op++] = ((in0 >>> 28) | ((in1 & 0x7) << 4)) & 0x7f; out[op++] = (in1 >>> 3) & 0x7f; out[op++] = (in1 >>> 10) & 0x7f; out[op++] = (in1 >>> 17) & 0x7f; out[op++] = (in1 >>> 24) & 0x7f; out[op++] = ((in1 >>> 31) | ((in2 & 0x3f) << 1)) & 0x7f; out[op++] = (in2 >>> 6) & 0x7f; out[op++] = (in2 >>> 13) & 0x7f; out[op++] = (in2 >>> 20) & 0x7f; out[op++] = ((in2 >>> 27) | ((in3 & 0x3) << 5)) & 0x7f; out[op++] = (in3 >>> 2) & 0x7f; out[op++] = (in3 >>> 9) & 0x7f; out[op++] = (in3 >>> 16) & 0x7f; out[op++] = (in3 >>> 23) & 0x7f; out[op++] = ((in3 >>> 30) | ((in4 & 0x1f) << 2)) & 0x7f; out[op++] = (in4 >>> 5) & 0x7f; out[op++] = (in4 >>> 12) & 0x7f; out[op++] = (in4 >>> 19) & 0x7f; out[op++] = ((in4 >>> 26) | ((in5 & 0x1) << 6)) & 0x7f; out[op++] = (in5 >>> 1) & 0x7f; out[op++] = (in5 >>> 8) & 0x7f; out[op++] = (in5 >>> 15) & 0x7f; out[op++] = (in5 >>> 22) & 0x7f; out[op++] = ((in5 >>> 29) | ((in6 & 0xf) << 3)) & 0x7f; out[op++] = (in6 >>> 4) & 0x7f; out[op++] = (in6 >>> 11) & 0x7f; out[op++] = (in6 >>> 18) & 0x7f; out[op] = (in6 >>> 25) & 0x7f; } export function fastUnpack32_8(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; const in0 = inValues[inPos] >>> 0; const in1 = inValues[inPos + 1] >>> 0; const in2 = inValues[inPos + 2] >>> 0; const in3 = inValues[inPos + 3] >>> 0; const in4 = inValues[inPos + 4] >>> 0; const in5 = inValues[inPos + 5] >>> 0; const in6 = inValues[inPos + 6] >>> 0; const in7 = inValues[inPos + 7] >>> 0; out[op++] = (in0 >>> 0) & 0xff; out[op++] = (in0 >>> 8) & 0xff; out[op++] = (in0 >>> 16) & 0xff; out[op++] = (in0 >>> 24) & 0xff; out[op++] = (in1 >>> 0) & 0xff; out[op++] = (in1 >>> 8) & 0xff; out[op++] = (in1 >>> 16) & 0xff; out[op++] = (in1 >>> 24) & 0xff; out[op++] = (in2 >>> 0) & 0xff; out[op++] = (in2 >>> 8) & 0xff; out[op++] = (in2 >>> 16) & 0xff; out[op++] = (in2 >>> 24) & 0xff; out[op++] = (in3 >>> 0) & 0xff; out[op++] = (in3 >>> 8) & 0xff; out[op++] = (in3 >>> 16) & 0xff; out[op++] = (in3 >>> 24) & 0xff; out[op++] = (in4 >>> 0) & 0xff; out[op++] = (in4 >>> 8) & 0xff; out[op++] = (in4 >>> 16) & 0xff; out[op++] = (in4 >>> 24) & 0xff; out[op++] = (in5 >>> 0) & 0xff; out[op++] = (in5 >>> 8) & 0xff; out[op++] = (in5 >>> 16) & 0xff; out[op++] = (in5 >>> 24) & 0xff; out[op++] = (in6 >>> 0) & 0xff; out[op++] = (in6 >>> 8) & 0xff; out[op++] = (in6 >>> 16) & 0xff; out[op++] = (in6 >>> 24) & 0xff; out[op++] = (in7 >>> 0) & 0xff; out[op++] = (in7 >>> 8) & 0xff; out[op++] = (in7 >>> 16) & 0xff; out[op] = (in7 >>> 24) & 0xff; } export function fastUnpack32_9(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; const in0 = inValues[inPos] >>> 0; const in1 = inValues[inPos + 1] >>> 0; const in2 = inValues[inPos + 2] >>> 0; const in3 = inValues[inPos + 3] >>> 0; const in4 = inValues[inPos + 4] >>> 0; const in5 = inValues[inPos + 5] >>> 0; const in6 = inValues[inPos + 6] >>> 0; const in7 = inValues[inPos + 7] >>> 0; const in8 = inValues[inPos + 8] >>> 0; out[op++] = (in0 >>> 0) & 0x1ff; out[op++] = (in0 >>> 9) & 0x1ff; out[op++] = (in0 >>> 18) & 0x1ff; out[op++] = ((in0 >>> 27) | ((in1 & 0xf) << 5)) & 0x1ff; out[op++] = (in1 >>> 4) & 0x1ff; out[op++] = (in1 >>> 13) & 0x1ff; out[op++] = (in1 >>> 22) & 0x1ff; out[op++] = ((in1 >>> 31) | ((in2 & 0xff) << 1)) & 0x1ff; out[op++] = (in2 >>> 8) & 0x1ff; out[op++] = (in2 >>> 17) & 0x1ff; out[op++] = ((in2 >>> 26) | ((in3 & 0x7) << 6)) & 0x1ff; out[op++] = (in3 >>> 3) & 0x1ff; out[op++] = (in3 >>> 12) & 0x1ff; out[op++] = (in3 >>> 21) & 0x1ff; out[op++] = ((in3 >>> 30) | ((in4 & 0x7f) << 2)) & 0x1ff; out[op++] = (in4 >>> 7) & 0x1ff; out[op++] = (in4 >>> 16) & 0x1ff; out[op++] = ((in4 >>> 25) | ((in5 & 0x3) << 7)) & 0x1ff; out[op++] = (in5 >>> 2) & 0x1ff; out[op++] = (in5 >>> 11) & 0x1ff; out[op++] = (in5 >>> 20) & 0x1ff; out[op++] = ((in5 >>> 29) | ((in6 & 0x3f) << 3)) & 0x1ff; out[op++] = (in6 >>> 6) & 0x1ff; out[op++] = (in6 >>> 15) & 0x1ff; out[op++] = ((in6 >>> 24) | ((in7 & 0x1) << 8)) & 0x1ff; out[op++] = (in7 >>> 1) & 0x1ff; out[op++] = (in7 >>> 10) & 0x1ff; out[op++] = (in7 >>> 19) & 0x1ff; out[op++] = ((in7 >>> 28) | ((in8 & 0x1f) << 4)) & 0x1ff; out[op++] = (in8 >>> 5) & 0x1ff; out[op++] = (in8 >>> 14) & 0x1ff; out[op] = (in8 >>> 23) & 0x1ff; } export function fastUnpack32_10(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; const in0 = inValues[inPos] >>> 0; const in1 = inValues[inPos + 1] >>> 0; const in2 = inValues[inPos + 2] >>> 0; const in3 = inValues[inPos + 3] >>> 0; const in4 = inValues[inPos + 4] >>> 0; const in5 = inValues[inPos + 5] >>> 0; const in6 = inValues[inPos + 6] >>> 0; const in7 = inValues[inPos + 7] >>> 0; const in8 = inValues[inPos + 8] >>> 0; const in9 = inValues[inPos + 9] >>> 0; out[op++] = (in0 >>> 0) & 0x3ff; out[op++] = (in0 >>> 10) & 0x3ff; out[op++] = (in0 >>> 20) & 0x3ff; out[op++] = ((in0 >>> 30) | ((in1 & 0xff) << 2)) & 0x3ff; out[op++] = (in1 >>> 8) & 0x3ff; out[op++] = (in1 >>> 18) & 0x3ff; out[op++] = ((in1 >>> 28) | ((in2 & 0x3f) << 4)) & 0x3ff; out[op++] = (in2 >>> 6) & 0x3ff; out[op++] = (in2 >>> 16) & 0x3ff; out[op++] = ((in2 >>> 26) | ((in3 & 0xf) << 6)) & 0x3ff; out[op++] = (in3 >>> 4) & 0x3ff; out[op++] = (in3 >>> 14) & 0x3ff; out[op++] = ((in3 >>> 24) | ((in4 & 0x3) << 8)) & 0x3ff; out[op++] = (in4 >>> 2) & 0x3ff; out[op++] = (in4 >>> 12) & 0x3ff; out[op++] = (in4 >>> 22) & 0x3ff; out[op++] = (in5 >>> 0) & 0x3ff; out[op++] = (in5 >>> 10) & 0x3ff; out[op++] = (in5 >>> 20) & 0x3ff; out[op++] = ((in5 >>> 30) | ((in6 & 0xff) << 2)) & 0x3ff; out[op++] = (in6 >>> 8) & 0x3ff; out[op++] = (in6 >>> 18) & 0x3ff; out[op++] = ((in6 >>> 28) | ((in7 & 0x3f) << 4)) & 0x3ff; out[op++] = (in7 >>> 6) & 0x3ff; out[op++] = (in7 >>> 16) & 0x3ff; out[op++] = ((in7 >>> 26) | ((in8 & 0xf) << 6)) & 0x3ff; out[op++] = (in8 >>> 4) & 0x3ff; out[op++] = (in8 >>> 14) & 0x3ff; out[op++] = ((in8 >>> 24) | ((in9 & 0x3) << 8)) & 0x3ff; out[op++] = (in9 >>> 2) & 0x3ff; out[op++] = (in9 >>> 12) & 0x3ff; out[op] = (in9 >>> 22) & 0x3ff; } export function fastUnpack32_11(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; const in0 = inValues[inPos] >>> 0; const in1 = inValues[inPos + 1] >>> 0; const in2 = inValues[inPos + 2] >>> 0; const in3 = inValues[inPos + 3] >>> 0; const in4 = inValues[inPos + 4] >>> 0; const in5 = inValues[inPos + 5] >>> 0; const in6 = inValues[inPos + 6] >>> 0; const in7 = inValues[inPos + 7] >>> 0; const in8 = inValues[inPos + 8] >>> 0; const in9 = inValues[inPos + 9] >>> 0; const in10 = inValues[inPos + 10] >>> 0; out[op++] = (in0 >>> 0) & 0x7ff; out[op++] = (in0 >>> 11) & 0x7ff; out[op++] = ((in0 >>> 22) | ((in1 & 0x1) << 10)) & 0x7ff; out[op++] = (in1 >>> 1) & 0x7ff; out[op++] = (in1 >>> 12) & 0x7ff; out[op++] = ((in1 >>> 23) | ((in2 & 0x3) << 9)) & 0x7ff; out[op++] = (in2 >>> 2) & 0x7ff; out[op++] = (in2 >>> 13) & 0x7ff; out[op++] = ((in2 >>> 24) | ((in3 & 0x7) << 8)) & 0x7ff; out[op++] = (in3 >>> 3) & 0x7ff; out[op++] = (in3 >>> 14) & 0x7ff; out[op++] = ((in3 >>> 25) | ((in4 & 0xf) << 7)) & 0x7ff; out[op++] = (in4 >>> 4) & 0x7ff; out[op++] = (in4 >>> 15) & 0x7ff; out[op++] = ((in4 >>> 26) | ((in5 & 0x1f) << 6)) & 0x7ff; out[op++] = (in5 >>> 5) & 0x7ff; out[op++] = (in5 >>> 16) & 0x7ff; out[op++] = ((in5 >>> 27) | ((in6 & 0x3f) << 5)) & 0x7ff; out[op++] = (in6 >>> 6) & 0x7ff; out[op++] = (in6 >>> 17) & 0x7ff; out[op++] = ((in6 >>> 28) | ((in7 & 0x7f) << 4)) & 0x7ff; out[op++] = (in7 >>> 7) & 0x7ff; out[op++] = (in7 >>> 18) & 0x7ff; out[op++] = ((in7 >>> 29) | ((in8 & 0xff) << 3)) & 0x7ff; out[op++] = (in8 >>> 8) & 0x7ff; out[op++] = (in8 >>> 19) & 0x7ff; out[op++] = ((in8 >>> 30) | ((in9 & 0x1ff) << 2)) & 0x7ff; out[op++] = (in9 >>> 9) & 0x7ff; out[op++] = (in9 >>> 20) & 0x7ff; out[op++] = ((in9 >>> 31) | ((in10 & 0x3ff) << 1)) & 0x7ff; out[op++] = (in10 >>> 10) & 0x7ff; out[op] = (in10 >>> 21) & 0x7ff; } export function fastUnpack32_12(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; const in0 = inValues[inPos] >>> 0; const in1 = inValues[inPos + 1] >>> 0; const in2 = inValues[inPos + 2] >>> 0; const in3 = inValues[inPos + 3] >>> 0; const in4 = inValues[inPos + 4] >>> 0; const in5 = inValues[inPos + 5] >>> 0; const in6 = inValues[inPos + 6] >>> 0; const in7 = inValues[inPos + 7] >>> 0; const in8 = inValues[inPos + 8] >>> 0; const in9 = inValues[inPos + 9] >>> 0; const in10 = inValues[inPos + 10] >>> 0; const in11 = inValues[inPos + 11] >>> 0; out[op++] = (in0 >>> 0) & 0xfff; out[op++] = (in0 >>> 12) & 0xfff; out[op++] = ((in0 >>> 24) | ((in1 & 0xf) << 8)) & 0xfff; out[op++] = (in1 >>> 4) & 0xfff; out[op++] = (in1 >>> 16) & 0xfff; out[op++] = ((in1 >>> 28) | ((in2 & 0xff) << 4)) & 0xfff; out[op++] = (in2 >>> 8) & 0xfff; out[op++] = (in2 >>> 20) & 0xfff; out[op++] = (in3 >>> 0) & 0xfff; out[op++] = (in3 >>> 12) & 0xfff; out[op++] = ((in3 >>> 24) | ((in4 & 0xf) << 8)) & 0xfff; out[op++] = (in4 >>> 4) & 0xfff; out[op++] = (in4 >>> 16) & 0xfff; out[op++] = ((in4 >>> 28) | ((in5 & 0xff) << 4)) & 0xfff; out[op++] = (in5 >>> 8) & 0xfff; out[op++] = (in5 >>> 20) & 0xfff; out[op++] = (in6 >>> 0) & 0xfff; out[op++] = (in6 >>> 12) & 0xfff; out[op++] = ((in6 >>> 24) | ((in7 & 0xf) << 8)) & 0xfff; out[op++] = (in7 >>> 4) & 0xfff; out[op++] = (in7 >>> 16) & 0xfff; out[op++] = ((in7 >>> 28) | ((in8 & 0xff) << 4)) & 0xfff; out[op++] = (in8 >>> 8) & 0xfff; out[op++] = (in8 >>> 20) & 0xfff; out[op++] = (in9 >>> 0) & 0xfff; out[op++] = (in9 >>> 12) & 0xfff; out[op++] = ((in9 >>> 24) | ((in10 & 0xf) << 8)) & 0xfff; out[op++] = (in10 >>> 4) & 0xfff; out[op++] = (in10 >>> 16) & 0xfff; out[op++] = ((in10 >>> 28) | ((in11 & 0xff) << 4)) & 0xfff; out[op++] = (in11 >>> 8) & 0xfff; out[op] = (in11 >>> 20) & 0xfff; } export function fastUnpack32_16(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; const in0 = inValues[inPos] >>> 0; const in1 = inValues[inPos + 1] >>> 0; const in2 = inValues[inPos + 2] >>> 0; const in3 = inValues[inPos + 3] >>> 0; const in4 = inValues[inPos + 4] >>> 0; const in5 = inValues[inPos + 5] >>> 0; const in6 = inValues[inPos + 6] >>> 0; const in7 = inValues[inPos + 7] >>> 0; const in8 = inValues[inPos + 8] >>> 0; const in9 = inValues[inPos + 9] >>> 0; const in10 = inValues[inPos + 10] >>> 0; const in11 = inValues[inPos + 11] >>> 0; const in12 = inValues[inPos + 12] >>> 0; const in13 = inValues[inPos + 13] >>> 0; const in14 = inValues[inPos + 14] >>> 0; const in15 = inValues[inPos + 15] >>> 0; out[op++] = (in0 >>> 0) & 0xffff; out[op++] = (in0 >>> 16) & 0xffff; out[op++] = (in1 >>> 0) & 0xffff; out[op++] = (in1 >>> 16) & 0xffff; out[op++] = (in2 >>> 0) & 0xffff; out[op++] = (in2 >>> 16) & 0xffff; out[op++] = (in3 >>> 0) & 0xffff; out[op++] = (in3 >>> 16) & 0xffff; out[op++] = (in4 >>> 0) & 0xffff; out[op++] = (in4 >>> 16) & 0xffff; out[op++] = (in5 >>> 0) & 0xffff; out[op++] = (in5 >>> 16) & 0xffff; out[op++] = (in6 >>> 0) & 0xffff; out[op++] = (in6 >>> 16) & 0xffff; out[op++] = (in7 >>> 0) & 0xffff; out[op++] = (in7 >>> 16) & 0xffff; out[op++] = (in8 >>> 0) & 0xffff; out[op++] = (in8 >>> 16) & 0xffff; out[op++] = (in9 >>> 0) & 0xffff; out[op++] = (in9 >>> 16) & 0xffff; out[op++] = (in10 >>> 0) & 0xffff; out[op++] = (in10 >>> 16) & 0xffff; out[op++] = (in11 >>> 0) & 0xffff; out[op++] = (in11 >>> 16) & 0xffff; out[op++] = (in12 >>> 0) & 0xffff; out[op++] = (in12 >>> 16) & 0xffff; out[op++] = (in13 >>> 0) & 0xffff; out[op++] = (in13 >>> 16) & 0xffff; out[op++] = (in14 >>> 0) & 0xffff; out[op++] = (in14 >>> 16) & 0xffff; out[op++] = (in15 >>> 0) & 0xffff; out[op] = (in15 >>> 16) & 0xffff; } export function fastUnpack256_1(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; let ip = inPos; for (let c = 0; c < 8; c++) { const in0 = inValues[ip++] >>> 0; out[op++] = (in0 >>> 0) & 0x1; out[op++] = (in0 >>> 1) & 0x1; out[op++] = (in0 >>> 2) & 0x1; out[op++] = (in0 >>> 3) & 0x1; out[op++] = (in0 >>> 4) & 0x1; out[op++] = (in0 >>> 5) & 0x1; out[op++] = (in0 >>> 6) & 0x1; out[op++] = (in0 >>> 7) & 0x1; out[op++] = (in0 >>> 8) & 0x1; out[op++] = (in0 >>> 9) & 0x1; out[op++] = (in0 >>> 10) & 0x1; out[op++] = (in0 >>> 11) & 0x1; out[op++] = (in0 >>> 12) & 0x1; out[op++] = (in0 >>> 13) & 0x1; out[op++] = (in0 >>> 14) & 0x1; out[op++] = (in0 >>> 15) & 0x1; out[op++] = (in0 >>> 16) & 0x1; out[op++] = (in0 >>> 17) & 0x1; out[op++] = (in0 >>> 18) & 0x1; out[op++] = (in0 >>> 19) & 0x1; out[op++] = (in0 >>> 20) & 0x1; out[op++] = (in0 >>> 21) & 0x1; out[op++] = (in0 >>> 22) & 0x1; out[op++] = (in0 >>> 23) & 0x1; out[op++] = (in0 >>> 24) & 0x1; out[op++] = (in0 >>> 25) & 0x1; out[op++] = (in0 >>> 26) & 0x1; out[op++] = (in0 >>> 27) & 0x1; out[op++] = (in0 >>> 28) & 0x1; out[op++] = (in0 >>> 29) & 0x1; out[op++] = (in0 >>> 30) & 0x1; out[op++] = (in0 >>> 31) & 0x1; } } export function fastUnpack256_2(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; let ip = inPos; for (let c = 0; c < 8; c++) { const in0 = inValues[ip++] >>> 0; const in1 = inValues[ip++] >>> 0; out[op++] = (in0 >>> 0) & 0x3; out[op++] = (in0 >>> 2) & 0x3; out[op++] = (in0 >>> 4) & 0x3; out[op++] = (in0 >>> 6) & 0x3; out[op++] = (in0 >>> 8) & 0x3; out[op++] = (in0 >>> 10) & 0x3; out[op++] = (in0 >>> 12) & 0x3; out[op++] = (in0 >>> 14) & 0x3; out[op++] = (in0 >>> 16) & 0x3; out[op++] = (in0 >>> 18) & 0x3; out[op++] = (in0 >>> 20) & 0x3; out[op++] = (in0 >>> 22) & 0x3; out[op++] = (in0 >>> 24) & 0x3; out[op++] = (in0 >>> 26) & 0x3; out[op++] = (in0 >>> 28) & 0x3; out[op++] = (in0 >>> 30) & 0x3; out[op++] = (in1 >>> 0) & 0x3; out[op++] = (in1 >>> 2) & 0x3; out[op++] = (in1 >>> 4) & 0x3; out[op++] = (in1 >>> 6) & 0x3; out[op++] = (in1 >>> 8) & 0x3; out[op++] = (in1 >>> 10) & 0x3; out[op++] = (in1 >>> 12) & 0x3; out[op++] = (in1 >>> 14) & 0x3; out[op++] = (in1 >>> 16) & 0x3; out[op++] = (in1 >>> 18) & 0x3; out[op++] = (in1 >>> 20) & 0x3; out[op++] = (in1 >>> 22) & 0x3; out[op++] = (in1 >>> 24) & 0x3; out[op++] = (in1 >>> 26) & 0x3; out[op++] = (in1 >>> 28) & 0x3; out[op++] = (in1 >>> 30) & 0x3; } } export function fastUnpack256_3(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; let ip = inPos; for (let c = 0; c < 8; c++) { const in0 = inValues[ip++] >>> 0; const in1 = inValues[ip++] >>> 0; const in2 = inValues[ip++] >>> 0; out[op++] = (in0 >>> 0) & 0x7; out[op++] = (in0 >>> 3) & 0x7; out[op++] = (in0 >>> 6) & 0x7; out[op++] = (in0 >>> 9) & 0x7; out[op++] = (in0 >>> 12) & 0x7; out[op++] = (in0 >>> 15) & 0x7; out[op++] = (in0 >>> 18) & 0x7; out[op++] = (in0 >>> 21) & 0x7; out[op++] = (in0 >>> 24) & 0x7; out[op++] = (in0 >>> 27) & 0x7; out[op++] = ((in0 >>> 30) | ((in1 & 0x1) << 2)) & 0x7; out[op++] = (in1 >>> 1) & 0x7; out[op++] = (in1 >>> 4) & 0x7; out[op++] = (in1 >>> 7) & 0x7; out[op++] = (in1 >>> 10) & 0x7; out[op++] = (in1 >>> 13) & 0x7; out[op++] = (in1 >>> 16) & 0x7; out[op++] = (in1 >>> 19) & 0x7; out[op++] = (in1 >>> 22) & 0x7; out[op++] = (in1 >>> 25) & 0x7; out[op++] = (in1 >>> 28) & 0x7; out[op++] = ((in1 >>> 31) | ((in2 & 0x3) << 1)) & 0x7; out[op++] = (in2 >>> 2) & 0x7; out[op++] = (in2 >>> 5) & 0x7; out[op++] = (in2 >>> 8) & 0x7; out[op++] = (in2 >>> 11) & 0x7; out[op++] = (in2 >>> 14) & 0x7; out[op++] = (in2 >>> 17) & 0x7; out[op++] = (in2 >>> 20) & 0x7; out[op++] = (in2 >>> 23) & 0x7; out[op++] = (in2 >>> 26) & 0x7; out[op++] = (in2 >>> 29) & 0x7; } } export function fastUnpack256_4(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; let ip = inPos; for (let c = 0; c < 8; c++) { const in0 = inValues[ip++] >>> 0; const in1 = inValues[ip++] >>> 0; const in2 = inValues[ip++] >>> 0; const in3 = inValues[ip++] >>> 0; out[op++] = (in0 >>> 0) & 0xf; out[op++] = (in0 >>> 4) & 0xf; out[op++] = (in0 >>> 8) & 0xf; out[op++] = (in0 >>> 12) & 0xf; out[op++] = (in0 >>> 16) & 0xf; out[op++] = (in0 >>> 20) & 0xf; out[op++] = (in0 >>> 24) & 0xf; out[op++] = (in0 >>> 28) & 0xf; out[op++] = (in1 >>> 0) & 0xf; out[op++] = (in1 >>> 4) & 0xf; out[op++] = (in1 >>> 8) & 0xf; out[op++] = (in1 >>> 12) & 0xf; out[op++] = (in1 >>> 16) & 0xf; out[op++] = (in1 >>> 20) & 0xf; out[op++] = (in1 >>> 24) & 0xf; out[op++] = (in1 >>> 28) & 0xf; out[op++] = (in2 >>> 0) & 0xf; out[op++] = (in2 >>> 4) & 0xf; out[op++] = (in2 >>> 8) & 0xf; out[op++] = (in2 >>> 12) & 0xf; out[op++] = (in2 >>> 16) & 0xf; out[op++] = (in2 >>> 20) & 0xf; out[op++] = (in2 >>> 24) & 0xf; out[op++] = (in2 >>> 28) & 0xf; out[op++] = (in3 >>> 0) & 0xf; out[op++] = (in3 >>> 4) & 0xf; out[op++] = (in3 >>> 8) & 0xf; out[op++] = (in3 >>> 12) & 0xf; out[op++] = (in3 >>> 16) & 0xf; out[op++] = (in3 >>> 20) & 0xf; out[op++] = (in3 >>> 24) & 0xf; out[op++] = (in3 >>> 28) & 0xf; } } export function fastUnpack256_5(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; let ip = inPos; for (let c = 0; c < 8; c++) { const in0 = inValues[ip++] >>> 0; const in1 = inValues[ip++] >>> 0; const in2 = inValues[ip++] >>> 0; const in3 = inValues[ip++] >>> 0; const in4 = inValues[ip++] >>> 0; out[op++] = (in0 >>> 0) & 0x1f; out[op++] = (in0 >>> 5) & 0x1f; out[op++] = (in0 >>> 10) & 0x1f; out[op++] = (in0 >>> 15) & 0x1f; out[op++] = (in0 >>> 20) & 0x1f; out[op++] = (in0 >>> 25) & 0x1f; out[op++] = ((in0 >>> 30) | ((in1 & 0x7) << 2)) & 0x1f; out[op++] = (in1 >>> 3) & 0x1f; out[op++] = (in1 >>> 8) & 0x1f; out[op++] = (in1 >>> 13) & 0x1f; out[op++] = (in1 >>> 18) & 0x1f; out[op++] = (in1 >>> 23) & 0x1f; out[op++] = ((in1 >>> 28) | ((in2 & 0x1) << 4)) & 0x1f; out[op++] = (in2 >>> 1) & 0x1f; out[op++] = (in2 >>> 6) & 0x1f; out[op++] = (in2 >>> 11) & 0x1f; out[op++] = (in2 >>> 16) & 0x1f; out[op++] = (in2 >>> 21) & 0x1f; out[op++] = (in2 >>> 26) & 0x1f; out[op++] = ((in2 >>> 31) | ((in3 & 0xf) << 1)) & 0x1f; out[op++] = (in3 >>> 4) & 0x1f; out[op++] = (in3 >>> 9) & 0x1f; out[op++] = (in3 >>> 14) & 0x1f; out[op++] = (in3 >>> 19) & 0x1f; out[op++] = (in3 >>> 24) & 0x1f; out[op++] = ((in3 >>> 29) | ((in4 & 0x3) << 3)) & 0x1f; out[op++] = (in4 >>> 2) & 0x1f; out[op++] = (in4 >>> 7) & 0x1f; out[op++] = (in4 >>> 12) & 0x1f; out[op++] = (in4 >>> 17) & 0x1f; out[op++] = (in4 >>> 22) & 0x1f; out[op++] = (in4 >>> 27) & 0x1f; } } export function fastUnpack256_6(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; let ip = inPos; for (let c = 0; c < 8; c++) { const in0 = inValues[ip++] >>> 0; const in1 = inValues[ip++] >>> 0; const in2 = inValues[ip++] >>> 0; const in3 = inValues[ip++] >>> 0; const in4 = inValues[ip++] >>> 0; const in5 = inValues[ip++] >>> 0; out[op++] = (in0 >>> 0) & 0x3f; out[op++] = (in0 >>> 6) & 0x3f; out[op++] = (in0 >>> 12) & 0x3f; out[op++] = (in0 >>> 18) & 0x3f; out[op++] = (in0 >>> 24) & 0x3f; out[op++] = ((in0 >>> 30) | ((in1 & 0xf) << 2)) & 0x3f; out[op++] = (in1 >>> 4) & 0x3f; out[op++] = (in1 >>> 10) & 0x3f; out[op++] = (in1 >>> 16) & 0x3f; out[op++] = (in1 >>> 22) & 0x3f; out[op++] = ((in1 >>> 28) | ((in2 & 0x3) << 4)) & 0x3f; out[op++] = (in2 >>> 2) & 0x3f; out[op++] = (in2 >>> 8) & 0x3f; out[op++] = (in2 >>> 14) & 0x3f; out[op++] = (in2 >>> 20) & 0x3f; out[op++] = (in2 >>> 26) & 0x3f; out[op++] = (in3 >>> 0) & 0x3f; out[op++] = (in3 >>> 6) & 0x3f; out[op++] = (in3 >>> 12) & 0x3f; out[op++] = (in3 >>> 18) & 0x3f; out[op++] = (in3 >>> 24) & 0x3f; out[op++] = ((in3 >>> 30) | ((in4 & 0xf) << 2)) & 0x3f; out[op++] = (in4 >>> 4) & 0x3f; out[op++] = (in4 >>> 10) & 0x3f; out[op++] = (in4 >>> 16) & 0x3f; out[op++] = (in4 >>> 22) & 0x3f; out[op++] = ((in4 >>> 28) | ((in5 & 0x3) << 4)) & 0x3f; out[op++] = (in5 >>> 2) & 0x3f; out[op++] = (in5 >>> 8) & 0x3f; out[op++] = (in5 >>> 14) & 0x3f; out[op++] = (in5 >>> 20) & 0x3f; out[op++] = (in5 >>> 26) & 0x3f; } } export function fastUnpack256_7(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; let ip = inPos; for (let c = 0; c < 8; c++) { const in0 = inValues[ip++] >>> 0; const in1 = inValues[ip++] >>> 0; const in2 = inValues[ip++] >>> 0; const in3 = inValues[ip++] >>> 0; const in4 = inValues[ip++] >>> 0; const in5 = inValues[ip++] >>> 0; const in6 = inValues[ip++] >>> 0; out[op++] = (in0 >>> 0) & 0x7f; out[op++] = (in0 >>> 7) & 0x7f; out[op++] = (in0 >>> 14) & 0x7f; out[op++] = (in0 >>> 21) & 0x7f; out[op++] = ((in0 >>> 28) | ((in1 & 0x7) << 4)) & 0x7f; out[op++] = (in1 >>> 3) & 0x7f; out[op++] = (in1 >>> 10) & 0x7f; out[op++] = (in1 >>> 17) & 0x7f; out[op++] = (in1 >>> 24) & 0x7f; out[op++] = ((in1 >>> 31) | ((in2 & 0x3f) << 1)) & 0x7f; out[op++] = (in2 >>> 6) & 0x7f; out[op++] = (in2 >>> 13) & 0x7f; out[op++] = (in2 >>> 20) & 0x7f; out[op++] = ((in2 >>> 27) | ((in3 & 0x3) << 5)) & 0x7f; out[op++] = (in3 >>> 2) & 0x7f; out[op++] = (in3 >>> 9) & 0x7f; out[op++] = (in3 >>> 16) & 0x7f; out[op++] = (in3 >>> 23) & 0x7f; out[op++] = ((in3 >>> 30) | ((in4 & 0x1f) << 2)) & 0x7f; out[op++] = (in4 >>> 5) & 0x7f; out[op++] = (in4 >>> 12) & 0x7f; out[op++] = (in4 >>> 19) & 0x7f; out[op++] = ((in4 >>> 26) | ((in5 & 0x1) << 6)) & 0x7f; out[op++] = (in5 >>> 1) & 0x7f; out[op++] = (in5 >>> 8) & 0x7f; out[op++] = (in5 >>> 15) & 0x7f; out[op++] = (in5 >>> 22) & 0x7f; out[op++] = ((in5 >>> 29) | ((in6 & 0xf) << 3)) & 0x7f; out[op++] = (in6 >>> 4) & 0x7f; out[op++] = (in6 >>> 11) & 0x7f; out[op++] = (in6 >>> 18) & 0x7f; out[op++] = (in6 >>> 25) & 0x7f; } } export function fastUnpack256_8(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; let ip = inPos; for (let c = 0; c < 8; c++) { const in0 = inValues[ip++] >>> 0; const in1 = inValues[ip++] >>> 0; const in2 = inValues[ip++] >>> 0; const in3 = inValues[ip++] >>> 0; const in4 = inValues[ip++] >>> 0; const in5 = inValues[ip++] >>> 0; const in6 = inValues[ip++] >>> 0; const in7 = inValues[ip++] >>> 0; out[op++] = (in0 >>> 0) & 0xff; out[op++] = (in0 >>> 8) & 0xff; out[op++] = (in0 >>> 16) & 0xff; out[op++] = (in0 >>> 24) & 0xff; out[op++] = (in1 >>> 0) & 0xff; out[op++] = (in1 >>> 8) & 0xff; out[op++] = (in1 >>> 16) & 0xff; out[op++] = (in1 >>> 24) & 0xff; out[op++] = (in2 >>> 0) & 0xff; out[op++] = (in2 >>> 8) & 0xff; out[op++] = (in2 >>> 16) & 0xff; out[op++] = (in2 >>> 24) & 0xff; out[op++] = (in3 >>> 0) & 0xff; out[op++] = (in3 >>> 8) & 0xff; out[op++] = (in3 >>> 16) & 0xff; out[op++] = (in3 >>> 24) & 0xff; out[op++] = (in4 >>> 0) & 0xff; out[op++] = (in4 >>> 8) & 0xff; out[op++] = (in4 >>> 16) & 0xff; out[op++] = (in4 >>> 24) & 0xff; out[op++] = (in5 >>> 0) & 0xff; out[op++] = (in5 >>> 8) & 0xff; out[op++] = (in5 >>> 16) & 0xff; out[op++] = (in5 >>> 24) & 0xff; out[op++] = (in6 >>> 0) & 0xff; out[op++] = (in6 >>> 8) & 0xff; out[op++] = (in6 >>> 16) & 0xff; out[op++] = (in6 >>> 24) & 0xff; out[op++] = (in7 >>> 0) & 0xff; out[op++] = (in7 >>> 8) & 0xff; out[op++] = (in7 >>> 16) & 0xff; out[op++] = (in7 >>> 24) & 0xff; } } export function fastUnpack256_16(inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number): void { let op = outPos; let ip = inPos; for (let i = 0; i < 128; i++) { const in0 = inValues[ip++] >>> 0; out[op++] = in0 & 0xffff; out[op++] = (in0 >>> 16) & 0xffff; } } export function fastUnpack256_Generic( inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number, bitWidth: number, ): void { const mask = MASKS[bitWidth] >>> 0; let inputWordIndex = inPos; let bitOffset = 0; let currentWord = inValues[inputWordIndex] >>> 0; let op = outPos; for (let c = 0; c < 8; c++) { for (let i = 0; i < 32; i++) { if (bitOffset + bitWidth <= 32) { const value = (currentWord >>> bitOffset) & mask; out[op + i] = value | 0; bitOffset += bitWidth; if (bitOffset === 32) { bitOffset = 0; inputWordIndex++; if (i !== 31) { currentWord = inValues[inputWordIndex] >>> 0; } } } else { const lowBits = 32 - bitOffset; const low = currentWord >>> bitOffset; inputWordIndex++; currentWord = inValues[inputWordIndex] >>> 0; const highBits = bitWidth - lowBits; const highMask = (-1 >>> (32 - highBits)) >>> 0; const high = currentWord & highMask; const value = (low | (high << lowBits)) & mask; out[op + i] = value | 0; bitOffset = highBits; } } op += 32; bitOffset = 0; if (c < 7) { currentWord = inValues[inputWordIndex] >>> 0; } } } ================================================ FILE: ts/src/decoding/fsstDecoder.spec.ts ================================================ import { describe, it, expect } from "vitest"; import { decodeFsst } from "./fsstDecoder"; import { encodeFsst, createSymbolTable } from "../encoding/fsstEncoder"; const textEncoder = new TextEncoder(); describe("decodeFsst", () => { describe("basic functionality", () => { it("should decode FSST compressed string data", () => { const inputString = "HelloWorld !"; const originalBytes = textEncoder.encode(inputString); const { symbols, symbolLengths } = createSymbolTable(["Hello", "World", "!"]); const encoded = encodeFsst(symbols, symbolLengths, originalBytes); const decoded = decodeFsst(symbols, symbolLengths, encoded); expect(decoded).toEqual(originalBytes); expect(new TextDecoder().decode(decoded)).toBe(inputString); }); it("should handle empty string", () => { const inputString = ""; const originalBytes = textEncoder.encode(inputString); const { symbols, symbolLengths } = createSymbolTable(["A"]); const encoded = encodeFsst(symbols, symbolLengths, originalBytes); const decoded = decodeFsst(symbols, symbolLengths, encoded); expect(decoded).toEqual(originalBytes); expect(decoded.length).toBe(0); }); it("should handle string with no matching symbols", () => { const inputString = "12345"; const originalBytes = textEncoder.encode(inputString); const { symbols, symbolLengths } = createSymbolTable(["abc", "def", "xyz"]); const encoded = encodeFsst(symbols, symbolLengths, originalBytes); const decoded = decodeFsst(symbols, symbolLengths, encoded); expect(decoded).toEqual(originalBytes); expect(new TextDecoder().decode(decoded)).toBe(inputString); }); it("should handle string with all matching symbols", () => { const inputString = "AAAA"; const originalBytes = textEncoder.encode(inputString); const { symbols, symbolLengths } = createSymbolTable(["A"]); const encoded = encodeFsst(symbols, symbolLengths, originalBytes); const decoded = decodeFsst(symbols, symbolLengths, encoded); expect(decoded).toEqual(originalBytes); expect(new TextDecoder().decode(decoded)).toBe(inputString); }); }); describe("compression verification", () => { it("should handle repeated strings efficiently", () => { const inputString = "HelloWorldHelloWorld"; const originalBytes = textEncoder.encode(inputString); const { symbols, symbolLengths } = createSymbolTable(["Hello", "World"]); const encoded = encodeFsst(symbols, symbolLengths, originalBytes); const decoded = decodeFsst(symbols, symbolLengths, encoded); expect(decoded).toEqual(originalBytes); expect(new TextDecoder().decode(decoded)).toBe(inputString); expect(encoded.length).toBeLessThan(originalBytes.length); }); }); }); ================================================ FILE: ts/src/decoding/fsstDecoder.ts ================================================ /** * Decode FSST compressed data * * @param symbols Array of symbols, where each symbol can be between 1 and 8 bytes * @param symbolLengths Array of symbol lengths, length of each symbol in symbols array * @param compressedData FSST Compressed data, where each entry is an index to the symbols array * @returns Decoded data as Uint8Array */ //TODO: improve -> quick and dirty implementation export function decodeFsst(symbols: Uint8Array, symbolLengths: Uint32Array, compressedData: Uint8Array): Uint8Array { //TODO: use typed array directly const decodedData: number[] = []; const symbolOffsets: number[] = new Array(symbolLengths.length).fill(0); for (let i = 1; i < symbolLengths.length; i++) { symbolOffsets[i] = symbolOffsets[i - 1] + symbolLengths[i - 1]; } for (let i = 0; i < compressedData.length; i++) { if (compressedData[i] === 255) { decodedData.push(compressedData[++i]); } else { const symbolLength = symbolLengths[compressedData[i]]; const symbolOffset = symbolOffsets[compressedData[i]]; for (let j = 0; j < symbolLength; j++) { decodedData.push(symbols[symbolOffset + j]); } } } return new Uint8Array(decodedData); } ================================================ FILE: ts/src/decoding/geometryDecoder.ts ================================================ import { decodeStreamMetadata, type MortonEncodedStreamMetadata } from "../metadata/tile/streamMetadataDecoder"; import type IntWrapper from "./intWrapper"; import { decodeSignedInt32Stream, decodeLengthStreamToOffsetBuffer, decodeUnsignedConstInt32Stream, decodeUnsignedInt32Stream, getVectorType, } from "./integerStreamDecoder"; import { VectorType } from "../vector/vectorType"; import { PhysicalStreamType } from "../metadata/tile/physicalStreamType"; import { LengthType } from "../metadata/tile/lengthType"; import { DictionaryType } from "../metadata/tile/dictionaryType"; import { createConstGeometryVector, createMortonEncodedConstGeometryVector, } from "../vector/geometry/constGeometryVector"; import { createFlatGeometryVector, createFlatGeometryVectorMortonEncoded } from "../vector/geometry/flatGeometryVector"; import { OffsetType } from "../metadata/tile/offsetType"; import { createConstGpuVector } from "../vector/geometry/constGpuVector"; import { createFlatGpuVector } from "../vector/geometry/flatGpuVector"; import type { GeometryVector, MortonSettings } from "../vector/geometry/geometryVector"; import type { GpuVector } from "../vector/geometry/gpuVector"; import type GeometryScaling from "./geometryScaling"; // TODO: get rid of numFeatures parameter export function decodeGeometryColumn( tile: Uint8Array, numStreams: number, offset: IntWrapper, numFeatures: number, scalingData?: GeometryScaling, ): GeometryVector | GpuVector { const geometryTypeMetadata = decodeStreamMetadata(tile, offset); const geometryTypesVectorType = getVectorType(geometryTypeMetadata, numFeatures, tile, offset); let vertexOffsets: Uint32Array | undefined; let vertexBuffer: Int32Array | Uint32Array | undefined; let mortonSettings: MortonSettings | undefined; let indexBuffer: Uint32Array | undefined; if (geometryTypesVectorType === VectorType.CONST) { /* All geometries in the column have the same geometry type */ const geometryType = decodeUnsignedConstInt32Stream(tile, offset, geometryTypeMetadata); // Variables for const geometry path (directly decoded as offsets) let geometryOffsets: Uint32Array | undefined; let partOffsets: Uint32Array | undefined; let ringOffsets: Uint32Array | undefined; //TODO: use geometryOffsets for that? -> but then tessellated polygons can't be used with normal polygons // in one FeatureTable? let triangleOffsets: Uint32Array | undefined; for (let i = 0; i < numStreams - 1; i++) { const geometryStreamMetadata = decodeStreamMetadata(tile, offset); switch (geometryStreamMetadata.physicalStreamType) { case PhysicalStreamType.LENGTH: switch (geometryStreamMetadata.logicalStreamType.lengthType) { case LengthType.GEOMETRIES: geometryOffsets = decodeLengthStreamToOffsetBuffer(tile, offset, geometryStreamMetadata); break; case LengthType.PARTS: partOffsets = decodeLengthStreamToOffsetBuffer(tile, offset, geometryStreamMetadata); break; case LengthType.RINGS: ringOffsets = decodeLengthStreamToOffsetBuffer(tile, offset, geometryStreamMetadata); break; case LengthType.TRIANGLES: triangleOffsets = decodeLengthStreamToOffsetBuffer(tile, offset, geometryStreamMetadata); } break; case PhysicalStreamType.OFFSET: { switch (geometryStreamMetadata.logicalStreamType.offsetType) { case OffsetType.VERTEX: vertexOffsets = decodeUnsignedInt32Stream(tile, offset, geometryStreamMetadata); break; case OffsetType.INDEX: indexBuffer = decodeUnsignedInt32Stream(tile, offset, geometryStreamMetadata); break; } break; } case PhysicalStreamType.DATA: { if (DictionaryType.VERTEX === geometryStreamMetadata.logicalStreamType.dictionaryType) { vertexBuffer = decodeSignedInt32Stream(tile, offset, geometryStreamMetadata, scalingData); } else { const mortonMetadata = geometryStreamMetadata as MortonEncodedStreamMetadata; mortonSettings = { numBits: mortonMetadata.numBits, coordinateShift: mortonMetadata.coordinateShift, }; vertexBuffer = decodeUnsignedInt32Stream(tile, offset, geometryStreamMetadata, scalingData); } break; } } } if (indexBuffer) { if (geometryOffsets !== undefined || partOffsets !== undefined) { /* Case when the indices of a Polygon outline are encoded in the tile */ const topologyVector = { geometryOffsets, partOffsets, ringOffsets }; return createConstGpuVector( numFeatures, geometryType, triangleOffsets, indexBuffer, vertexBuffer, topologyVector, ); } /* Case when the no Polygon outlines are encoded in the tile */ return createConstGpuVector(numFeatures, geometryType, triangleOffsets, indexBuffer, vertexBuffer); } return mortonSettings === undefined ? /* Currently only 2D coordinates (Vec2) are implemented in the encoder */ createConstGeometryVector( numFeatures, geometryType, { geometryOffsets, partOffsets, ringOffsets }, vertexOffsets, vertexBuffer, ) : createMortonEncodedConstGeometryVector( numFeatures, geometryType, { geometryOffsets, partOffsets, ringOffsets }, vertexOffsets, vertexBuffer, mortonSettings, ); } /* Different geometry types are mixed in the geometry column */ const geometryTypeVector = decodeUnsignedInt32Stream(tile, offset, geometryTypeMetadata); // Variables for flat geometry path (decoded as lengths, then converted to offsets) let geometryLengths: Uint32Array | undefined; let partLengths: Uint32Array | undefined; let ringLengths: Uint32Array | undefined; //TODO: use geometryOffsets for that? -> but then tessellated polygons can't be used with normal polygons // in one FeatureTable? let triangleOffsets: Uint32Array | undefined; for (let i = 0; i < numStreams - 1; i++) { const geometryStreamMetadata = decodeStreamMetadata(tile, offset); switch (geometryStreamMetadata.physicalStreamType) { case PhysicalStreamType.LENGTH: switch (geometryStreamMetadata.logicalStreamType.lengthType) { case LengthType.GEOMETRIES: geometryLengths = decodeUnsignedInt32Stream(tile, offset, geometryStreamMetadata); break; case LengthType.PARTS: partLengths = decodeUnsignedInt32Stream(tile, offset, geometryStreamMetadata); break; case LengthType.RINGS: ringLengths = decodeUnsignedInt32Stream(tile, offset, geometryStreamMetadata); break; case LengthType.TRIANGLES: triangleOffsets = decodeLengthStreamToOffsetBuffer(tile, offset, geometryStreamMetadata); } break; case PhysicalStreamType.OFFSET: switch (geometryStreamMetadata.logicalStreamType.offsetType) { case OffsetType.VERTEX: vertexOffsets = decodeUnsignedInt32Stream(tile, offset, geometryStreamMetadata); break; case OffsetType.INDEX: indexBuffer = decodeUnsignedInt32Stream(tile, offset, geometryStreamMetadata); break; } break; case PhysicalStreamType.DATA: if (DictionaryType.VERTEX === geometryStreamMetadata.logicalStreamType.dictionaryType) { vertexBuffer = decodeSignedInt32Stream(tile, offset, geometryStreamMetadata, scalingData); } else { const mortonMetadata = geometryStreamMetadata as MortonEncodedStreamMetadata; mortonSettings = { numBits: mortonMetadata.numBits, coordinateShift: mortonMetadata.coordinateShift, }; vertexBuffer = decodeUnsignedInt32Stream(tile, offset, geometryStreamMetadata, scalingData); } break; } } // TODO: refactor the following instructions -> decode in one pass for performance reasons /* Calculate the offsets from the length buffer for util access */ let geometryOffsets: Uint32Array | undefined; let partOffsets: Uint32Array | undefined; let ringOffsets: Uint32Array | undefined; if (geometryLengths) { geometryOffsets = decodeRootLengthStream(geometryTypeVector, geometryLengths, 2); if (partLengths && ringLengths) { partOffsets = decodeLevel1LengthStream(geometryTypeVector, geometryOffsets, partLengths, false); ringOffsets = decodeLevel2LengthStream(geometryTypeVector, geometryOffsets, partOffsets, ringLengths); } else if (partLengths) { partOffsets = decodeLevel1WithoutRingBufferLengthStream(geometryTypeVector, geometryOffsets, partLengths); } } else if (partLengths && ringLengths) { partOffsets = decodeRootLengthStream(geometryTypeVector, partLengths, 1); ringOffsets = decodeLevel1LengthStream(geometryTypeVector, partOffsets, ringLengths, true); } else if (partLengths) { partOffsets = decodeRootLengthStream(geometryTypeVector, partLengths, 0); } if (indexBuffer && !partOffsets) { /* Case when the indices of a Polygon outline are not encoded in the data so no * topology data are present in the tile */ return createFlatGpuVector(geometryTypeVector, triangleOffsets, indexBuffer, vertexBuffer); } if (indexBuffer) { /* Case when the indices of a Polygon outline are encoded in the tile */ return createFlatGpuVector(geometryTypeVector, triangleOffsets, indexBuffer, vertexBuffer, { geometryOffsets, partOffsets, ringOffsets, }); } return mortonSettings === undefined /* Currently only 2D coordinates (Vec2) are implemented in the encoder */ ? createFlatGeometryVector( geometryTypeVector, { geometryOffsets, partOffsets, ringOffsets }, vertexOffsets, vertexBuffer, ) : createFlatGeometryVectorMortonEncoded( geometryTypeVector, { geometryOffsets, partOffsets, ringOffsets }, vertexOffsets, vertexBuffer, mortonSettings, ); } /* * Handle the parsing of the different topology length buffers separate not generic to reduce the * branching and improve the performance */ function decodeRootLengthStream( geometryTypes: Uint32Array, rootLengthStream: Uint32Array, bufferId: number, ): Uint32Array { const rootBufferOffsets = new Uint32Array(geometryTypes.length + 1); let previousOffset = 0; rootBufferOffsets[0] = previousOffset; let rootLengthCounter = 0; for (let i = 0; i < geometryTypes.length; i++) { /* Test if the geometry has and entry in the root buffer * BufferId: 2 GeometryOffsets -> MultiPolygon, MultiLineString, MultiPoint * BufferId: 1 PartOffsets -> Polygon * BufferId: 0 PartOffsets, RingOffsets -> LineString * */ previousOffset = rootBufferOffsets[i + 1] = previousOffset + (geometryTypes[i] > bufferId ? rootLengthStream[rootLengthCounter++] : 1); } return rootBufferOffsets; } function decodeLevel1LengthStream( geometryTypes: Uint32Array, rootOffsetBuffer: Uint32Array, level1LengthBuffer: Uint32Array, isLineStringPresent: boolean, ): Uint32Array { const level1BufferOffsets = new Uint32Array(rootOffsetBuffer[rootOffsetBuffer.length - 1] + 1); let previousOffset = 0; level1BufferOffsets[0] = previousOffset; let level1BufferCounter = 1; let level1LengthBufferCounter = 0; for (let i = 0; i < geometryTypes.length; i++) { const geometryType = geometryTypes[i]; const numGeometries = rootOffsetBuffer[i + 1] - rootOffsetBuffer[i]; if ( geometryType === 5 || geometryType === 2 || (isLineStringPresent && (geometryType === 4 || geometryType === 1)) ) { /* For MultiPolygon, Polygon and in some cases for MultiLineString and LineString * a value in the level1LengthBuffer exists */ for (let j = 0; j < numGeometries; j++) { previousOffset = level1BufferOffsets[level1BufferCounter++] = previousOffset + level1LengthBuffer[level1LengthBufferCounter++]; } } else { /* For MultiPoint and Point and in some cases for MultiLineString and LineString no value in the * level1LengthBuffer exists */ for (let j = 0; j < numGeometries; j++) { level1BufferOffsets[level1BufferCounter++] = ++previousOffset; } } } return level1BufferOffsets; } /* * Case where no ring buffer exists so no MultiPolygon or Polygon geometry is part of the buffer */ function decodeLevel1WithoutRingBufferLengthStream( geometryTypes: Uint32Array, rootOffsetBuffer: Uint32Array, level1LengthBuffer: Uint32Array, ): Uint32Array { const level1BufferOffsets = new Uint32Array(rootOffsetBuffer[rootOffsetBuffer.length - 1] + 1); let previousOffset = 0; level1BufferOffsets[0] = previousOffset; let level1OffsetBufferCounter = 1; let level1LengthCounter = 0; for (let i = 0; i < geometryTypes.length; i++) { const geometryType = geometryTypes[i]; const numGeometries = rootOffsetBuffer[i + 1] - rootOffsetBuffer[i]; if (geometryType === 4 || geometryType === 1) { /* For MultiLineString and LineString a value in the level1LengthBuffer exists */ for (let j = 0; j < numGeometries; j++) { previousOffset = level1BufferOffsets[level1OffsetBufferCounter++] = previousOffset + level1LengthBuffer[level1LengthCounter++]; } } else { /* For MultiPoint and Point no value in level1LengthBuffer exists */ for (let j = 0; j < numGeometries; j++) { level1BufferOffsets[level1OffsetBufferCounter++] = ++previousOffset; } } } return level1BufferOffsets; } function decodeLevel2LengthStream( geometryTypes: Uint32Array, rootOffsetBuffer: Uint32Array, level1OffsetBuffer: Uint32Array, level2LengthBuffer: Uint32Array, ): Uint32Array { const level2BufferOffsets = new Uint32Array(level1OffsetBuffer[level1OffsetBuffer.length - 1] + 1); let previousOffset = 0; level2BufferOffsets[0] = previousOffset; let level1OffsetBufferCounter = 1; let level2OffsetBufferCounter = 1; let level2LengthBufferCounter = 0; for (let i = 0; i < geometryTypes.length; i++) { const geometryType = geometryTypes[i]; const numGeometries = rootOffsetBuffer[i + 1] - rootOffsetBuffer[i]; if (geometryType !== 0 && geometryType !== 3) { /* For MultiPolygon, MultiLineString, Polygon and LineString a value in level2LengthBuffer * exists */ for (let j = 0; j < numGeometries; j++) { const numParts = level1OffsetBuffer[level1OffsetBufferCounter] - level1OffsetBuffer[level1OffsetBufferCounter - 1]; level1OffsetBufferCounter++; for (let k = 0; k < numParts; k++) { previousOffset = level2BufferOffsets[level2OffsetBufferCounter++] = previousOffset + level2LengthBuffer[level2LengthBufferCounter++]; } } } else { /* For MultiPoint and Point no value in level2LengthBuffer exists */ for (let j = 0; j < numGeometries; j++) { level2BufferOffsets[level2OffsetBufferCounter++] = ++previousOffset; level1OffsetBufferCounter++; } } } return level2BufferOffsets; } ================================================ FILE: ts/src/decoding/geometryScaling.ts ================================================ export default interface GeometryScaling { extent: number; min: number; max: number; scale?: number; } ================================================ FILE: ts/src/decoding/intWrapper.ts ================================================ // Ported from https://github.com/lemire/JavaFastPFOR/blob/master/src/main/java/me/lemire/integercompression/IntWrapper.java export default class IntWrapper { constructor(private value: number) {} public get(): number { return this.value; } public set(v: number): void { this.value = v; } public increment(): number { return this.value++; } public add(v: number): void { this.value += v; } } ================================================ FILE: ts/src/decoding/integerDecodingUtils.spec.ts ================================================ import { describe, it, expect } from "vitest"; import { createFastPforWireDecodeWorkspace, decodeFastPfor, decodeFastPforWithWorkspace, decodeVarintInt32, decodeVarintInt64, decodeVarintFloat64, decodeZigZagInt32, decodeZigZagInt64, decodeZigZagFloat64, decodeZigZagInt32Value, decodeZigZagInt64Value, decodeUnsignedRleInt32, decodeUnsignedRleInt64, decodeUnsignedRleFloat64, decodeZigZagDeltaInt64, decodeDeltaRleInt32, decodeDeltaRleInt64, decodeUnsignedConstRleInt64, decodeZigZagConstRleInt64, decodeZigZagSequenceRleInt64, decodeZigZagRleInt32, decodeZigZagRleInt64, decodeZigZagRleFloat64, decodeZigZagRleDeltaInt32, fastInverseDelta, decodeZigZagSequenceRleInt32, decodeZigZagDeltaInt32, decodeZigZagDeltaFloat64, decodeRleDeltaInt32, decodeComponentwiseDeltaVec2, decodeComponentwiseDeltaVec2Scaled, } from "./integerDecodingUtils"; import IntWrapper from "./intWrapper"; import { encodeVarintInt32, encodeVarintInt64, encodeDeltaInt32, encodeDeltaRleInt32, encodeDeltaRleInt64, encodeUnsignedRleFloat64, encodeUnsignedRleInt32, encodeUnsignedRleInt64, encodeZigZagDeltaInt64, encodeZigZagFloat64, encodeZigZagInt32, encodeZigZagInt32Value, encodeZigZagInt64, encodeZigZagInt64Value, encodeZigZagRleFloat64, encodeZigZagRleInt32, encodeZigZagRleInt64, encodeZigZagDeltaInt32, encodeZigZagDeltaFloat64, encodeVarintFloat64, encodeZigZagRleDeltaInt32, encodeRleDeltaInt32, encodeComponentwiseDeltaVec2, encodeComponentwiseDeltaVec2Scaled, } from "../encoding/integerEncodingUtils"; describe("IntegerDecodingUtils", () => { describe("Varint decoding", () => { it("should decode Int32", () => { const value = 2 ** 10; const encoded = encodeVarintInt32(new Uint32Array([value])); const decoded = decodeVarintInt32(encoded, new IntWrapper(0), 1); expect(decoded[0]).toEqual(value); }); it("should decode Int64", () => { const value = 2n ** 50n; const encoded = encodeVarintInt64(new BigUint64Array([value])); const decoded = decodeVarintInt64(encoded, new IntWrapper(0), 1); expect(decoded[0]).toEqual(value); }); it("should return valid decoded values for varint long to float64", () => { const value = 2 ** 40; const varintEncoded = encodeVarintFloat64(new Float64Array([value])); const actualValues = decodeVarintFloat64(varintEncoded, new IntWrapper(0), 1); expect(actualValues[0]).toEqual(value); }); }); describe("ZigZag encoding", () => { it("should decode zigzag Int32Array", () => { const data = new Int32Array([0, 1, 2, 3]); const encoded = encodeZigZagInt32(data); const decoded = decodeZigZagInt32(encoded); expect(Array.from(decoded)).toEqual([0, 1, 2, 3]); }); it("should decode zigzag BigInt64Array", () => { const data = new BigInt64Array([0n, 1n, 2n, 3n]); const encoded = encodeZigZagInt64(data); const decoded = decodeZigZagInt64(encoded); expect(Array.from(decoded)).toEqual([0n, 1n, 2n, 3n]); }); it("should decode zigzag Float64Array", () => { const value = 2 ** 35; const data = new Float64Array([value]); encodeZigZagFloat64(data); decodeZigZagFloat64(data); expect(Array.from(data)).toEqual([value]); }); it("should decode single Int32 zigzag values", () => { expect(encodeZigZagInt32Value(decodeZigZagInt32Value(0))).toBe(0); expect(encodeZigZagInt32Value(decodeZigZagInt32Value(1))).toBe(1); expect(encodeZigZagInt32Value(decodeZigZagInt32Value(2))).toBe(2); }); it("should decode single BigInt zigzag values", () => { expect(encodeZigZagInt64Value(decodeZigZagInt64Value(0n))).toBe(0n); expect(encodeZigZagInt64Value(decodeZigZagInt64Value(1n))).toBe(1n); }); }); describe("RLE decoding", () => { describe("Unsigned RLE", () => { it("should decode empty unsigned RLE", () => { const data = new Uint32Array([]); const encodedRle = encodeUnsignedRleInt32(data); const decoded = decodeUnsignedRleInt32(encodedRle.data, encodedRle.runs, data.length); expect(Array.from(decoded)).toEqual([]); }); it("should decode unsigned RLE", () => { const data = new Uint32Array([10, 10, 20, 20, 20]); const encodedRle = encodeUnsignedRleInt32(data); const decoded = decodeUnsignedRleInt32(encodedRle.data, encodedRle.runs, data.length); expect(Array.from(decoded)).toEqual([10, 10, 20, 20, 20]); }); it("should decode empty unsigned RLE Int64", () => { const data = new BigInt64Array([]); const encodedRle = encodeUnsignedRleInt64(data); const decoded = decodeUnsignedRleInt64(encodedRle.data, encodedRle.runs, data.length); expect(Array.from(decoded)).toEqual([]); }); it("should decode unsigned RLE Int64", () => { const data = new BigInt64Array([10n, 10n, 20n, 20n, 20n]); const encodedRle = encodeUnsignedRleInt64(data); const decoded = decodeUnsignedRleInt64(encodedRle.data, encodedRle.runs, data.length); expect(Array.from(decoded)).toEqual([10n, 10n, 20n, 20n, 20n]); }); it("should decode empty unsigned RLE Float64", () => { const data = new Float64Array([]); const encodedRle = encodeUnsignedRleFloat64(data); const decoded = decodeUnsignedRleFloat64(encodedRle.data, encodedRle.runs, data.length); expect(Array.from(decoded)).toEqual([]); }); it("should decode unsigned RLE Float64", () => { const data = new Float64Array([10.5, 10.5, 20.5, 20.5, 20.5]); const encodedRle = encodeUnsignedRleFloat64(data); const decoded = decodeUnsignedRleFloat64(encodedRle.data, encodedRle.runs, data.length); expect(Array.from(decoded)).toEqual([10.5, 10.5, 20.5, 20.5, 20.5]); }); }); describe("ZigZag RLE", () => { it("should decode empty ZigZag RLE Int32", () => { const data = new Int32Array([]); const encoded = encodeZigZagRleInt32(data); const decoded = decodeZigZagRleInt32(encoded.data, encoded.runs, encoded.numTotalValues); expect(Array.from(decoded)).toEqual([]); }); it("should decode ZigZag RLE Int32", () => { const encoded = new Int32Array([2, 2, 3, 3, 3]); const encodedData = encodeZigZagRleInt32(encoded); const decoded = decodeZigZagRleInt32(encodedData.data, encodedData.runs, encodedData.numTotalValues); expect(Array.from(decoded)).toEqual([2, 2, 3, 3, 3]); }); it("should decode empty ZigZag RLE Int64", () => { const data = new BigInt64Array([]); const encoded = encodeZigZagRleInt64(data); const decoded = decodeZigZagRleInt64(encoded.data, encoded.runs, encoded.numTotalValues); expect(Array.from(decoded)).toEqual([]); }); it("should decode ZigZag RLE Int64", () => { const encoded = new BigInt64Array([2n, 2n, 3n, 3n, 3n]); const encodedData = encodeZigZagRleInt64(encoded); const decoded = decodeZigZagRleInt64(encodedData.data, encodedData.runs, encodedData.numTotalValues); expect(Array.from(decoded)).toEqual([2n, 2n, 3n, 3n, 3n]); }); it("should decode empty ZigZag RLE Float64", () => { const data = new Float64Array([]); const encoded = encodeZigZagRleFloat64(data); const decoded = decodeZigZagRleFloat64(encoded.data, encoded.runs, encoded.numTotalValues); expect(Array.from(decoded)).toEqual([]); }); it("should decode ZigZag RLE Float64", () => { const encoded = new Float64Array([2, 2, 3, 3, 3]); const encodedData = encodeZigZagRleFloat64(encoded); const decoded = decodeZigZagRleFloat64(encodedData.data, encodedData.runs, encodedData.numTotalValues); expect(Array.from(decoded)).toEqual([2, 2, 3, 3, 3]); }); }); }); describe("Delta encoding", () => { describe("ZigZag Delta", () => { it("should decode zigzag delta Int32", () => { const data = new Int32Array([1, 2, 3, 5, 6, 7]); const encoded = encodeZigZagDeltaInt32(data); const decoded = decodeZigZagDeltaInt32(encoded); expect(Array.from(decoded)).toEqual([1, 2, 3, 5, 6, 7]); }); it("should decode zigzag delta Int64", () => { const data = new BigInt64Array([1n, 2n, 3n, 5n, 6n, 7n]); const encoded = encodeZigZagDeltaInt64(data); const decoded = decodeZigZagDeltaInt64(encoded); expect(Array.from(decoded)).toEqual([1n, 2n, 3n, 5n, 6n, 7n]); }); it("should decode zigzag delta Float64", () => { const data = new Float64Array([1.0, 2.0, 3.0, 5.0, 6.0, 7.0]); encodeZigZagDeltaFloat64(data); decodeZigZagDeltaFloat64(data); expect(Array.from(data)).toEqual([1.0, 2.0, 3.0, 5.0, 6.0, 7.0]); }); }); describe("Fast inverse delta", () => { it("should apply fast inverse delta", () => { const data = new Int32Array([10, 15, 18, 20]); fastInverseDelta(data); encodeDeltaInt32(data); expect(Array.from(data)).toEqual([10, 15, 18, 20]); }); }); describe("Componentwise Delta Vec2", () => { it("should decode empty array", () => { const data = new Int32Array([]); const expected = new Int32Array(data); const encoded = encodeComponentwiseDeltaVec2(data); const decoded = decodeComponentwiseDeltaVec2(encoded); expect(Array.from(decoded)).toEqual(Array.from(expected)); }); it("should decode single vertex", () => { const data = new Int32Array([10, 20]); const expected = new Int32Array(data); const encoded = encodeComponentwiseDeltaVec2(data); const decoded = decodeComponentwiseDeltaVec2(encoded); expect(Array.from(decoded)).toEqual(Array.from(expected)); }); it("should decode many vertices (unrolled loop test)", () => { const data = new Int32Array([0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9]); const expected = new Int32Array(data); const encoded = encodeComponentwiseDeltaVec2(data); const decoded = decodeComponentwiseDeltaVec2(encoded); expect(Array.from(decoded)).toEqual(Array.from(expected)); }); }); describe("Componentwise Delta Vec2 Scaled", () => { const scale = 2.0; const min = 0; const max = 4096; it("should decode empty array", () => { const data = new Int32Array([]); const expected = new Int32Array(data); const encoded = encodeComponentwiseDeltaVec2Scaled(data, scale); const decoded = decodeComponentwiseDeltaVec2Scaled(encoded, scale, min, max); expect(Array.from(decoded)).toEqual(Array.from(expected)); }); it("should decode single vertex", () => { const data = new Int32Array([100, 200]); const expected = new Int32Array(data); const encoded = encodeComponentwiseDeltaVec2Scaled(data, scale); const decoded = decodeComponentwiseDeltaVec2Scaled(encoded, scale, min, max); expect(Array.from(decoded)).toEqual(Array.from(expected)); }); it("should decode with different scale", () => { const testScale = 10.0; const data = new Int32Array([1000, 2000, 1100, 2200]); const expected = new Int32Array(data); const encoded = encodeComponentwiseDeltaVec2Scaled(data, testScale); const decoded = decodeComponentwiseDeltaVec2Scaled(encoded, testScale, min, max); expect(Array.from(decoded)).toEqual(Array.from(expected)); }); it("should decode many vertices (unrolled loop test)", () => { const numbers: number[] = []; for (let i = 0; i < 100; i++) { numbers.push(i * 10, i * 10); } const data = new Int32Array(numbers); const expected = new Int32Array(data); const encoded = encodeComponentwiseDeltaVec2Scaled(data, scale); const decoded = decodeComponentwiseDeltaVec2Scaled(encoded, scale, min, max); expect(Array.from(decoded)).toEqual(Array.from(expected)); }); }); describe("Delta RLE", () => { it("should decode empty delta RLE Int32", () => { const data = new Int32Array([]); const encoded = encodeDeltaRleInt32(data); const decoded = decodeDeltaRleInt32(encoded.data, encoded.runs, encoded.numValues); expect(Array.from(decoded)).toEqual([]); }); it("should decode delta RLE Int32", () => { const data = new Int32Array([1, 2, 3, 5, 6, 7]); const encoded = encodeDeltaRleInt32(data); const decoded = decodeDeltaRleInt32(encoded.data, encoded.runs, encoded.numValues); expect(Array.from(decoded)).toEqual([1, 2, 3, 5, 6, 7]); }); it("should decode empty delta RLE Int64", () => { const data = new BigInt64Array([]); const encoded = encodeDeltaRleInt64(data); const decoded = decodeDeltaRleInt64(encoded.data, encoded.runs, encoded.numValues); expect(Array.from(decoded)).toEqual([]); }); it("should decode delta RLE Int64", () => { const data = new BigInt64Array([1n, 2n, 3n, 5n, 6n, 7n]); const encoded = encodeDeltaRleInt64(data); const decoded = decodeDeltaRleInt64(encoded.data, encoded.runs, encoded.numValues); expect(Array.from(decoded)).toEqual([1n, 2n, 3n, 5n, 6n, 7n]); }); }); describe("ZigZag RLE Delta", () => { it("should decode zigzag RLE delta", () => { const data = new Int32Array([1, 2, 3, 4]); const encoded = encodeZigZagRleDeltaInt32(data); const decoded = decodeZigZagRleDeltaInt32(encoded.data, encoded.runs, encoded.numTotalValues); // The decoder is adding a 0 at the start expect(Array.from(decoded)).toEqual([0, 1, 2, 3, 4]); }); it("should decode RLE delta", () => { const data = new Uint32Array([1, 2, 3, 4]); const encoded = encodeRleDeltaInt32(data); const decoded = decodeRleDeltaInt32(encoded.data, encoded.runs, encoded.numTotalValues); // The decoder is adding a 0 at the start expect(Array.from(decoded)).toEqual([0, 1, 2, 3, 4]); }); }); }); describe("Const and Sequence RLE", () => { it("should decode unsigned const RLE Int64", () => { const data = new BigInt64Array([5n, 42n]); expect(decodeUnsignedConstRleInt64(data)).toBe(42n); }); it("should decode zigzag const RLE Int64", () => { const data = new BigInt64Array([5n, encodeZigZagInt64Value(2n)]); expect(decodeZigZagConstRleInt64(data)).toBe(2n); }); it("should decode zigzag sequence RLE Int32", () => { const data = new Int32Array([5, 2]); const [base, delta] = decodeZigZagSequenceRleInt32(data); expect(base).toBe(1); expect(delta).toBe(1); }); it("should decode zigzag sequence RLE Int32 with delta", () => { const data = new Int32Array([5, 2, 5, 2]); const [base, delta] = decodeZigZagSequenceRleInt32(data); expect(base).toBe(-3); expect(delta).toBe(1); }); it("should decode zigzag sequence RLE Int64", () => { const data = new BigInt64Array([5n, 2n]); const [base, delta] = decodeZigZagSequenceRleInt64(data); expect(base).toBe(1n); expect(delta).toBe(1n); }); it("should decode zigzag sequence RLE Int64 with delta", () => { const data = new BigInt64Array([5n, 2n, 5n, 2n]); const [base, delta] = decodeZigZagSequenceRleInt64(data); expect(base).toBe(-3n); expect(delta).toBe(1n); }); }); it("should reject FastPFOR byte lengths that are not multiple of 4", () => { const encoded = new Uint8Array([0x01, 0x02, 0x03]); const offset = new IntWrapper(0); expect(() => decodeFastPfor(encoded, 0, encoded.length, offset)).toThrow(/invalid encodedByteLength=3/); expect(offset.get()).toBe(0); }); it("should reject FastPFOR byte lengths with workspace API when not multiple of 4", () => { const encoded = new Uint8Array([0x01, 0x02, 0x03]); const offset = new IntWrapper(0); const workspace = createFastPforWireDecodeWorkspace(); expect(() => decodeFastPforWithWorkspace(encoded, 0, encoded.length, offset, workspace)).toThrow( /invalid encodedByteLength=3/, ); expect(offset.get()).toBe(0); }); }); ================================================ FILE: ts/src/decoding/integerDecodingUtils.ts ================================================ import type IntWrapper from "./intWrapper"; import { createFastPforWireDecodeWorkspace, decodeFastPforInt32, ensureFastPforWireEncodedWordsCapacity, type FastPforWireDecodeWorkspace, } from "./fastPforDecoder"; import { decodeBigEndianInt32sInto } from "./bigEndianDecode"; export type { FastPforWireDecodeWorkspace } from "./fastPforDecoder"; export { createFastPforWireDecodeWorkspace } from "./fastPforDecoder"; //based on https://github.com/mapbox/pbf/blob/main/index.js export function decodeVarintInt32(buf: Uint8Array, bufferOffset: IntWrapper, numValues: number): Uint32Array { const dst = new Uint32Array(numValues); let dstOffset = 0; let offset = bufferOffset.get(); for (let i = 0; i < dst.length; i++) { let b = buf[offset++]; let val = b & 0x7f; if (b < 0x80) { dst[dstOffset++] = val; continue; } b = buf[offset++]; val |= (b & 0x7f) << 7; if (b < 0x80) { dst[dstOffset++] = val; continue; } b = buf[offset++]; val |= (b & 0x7f) << 14; if (b < 0x80) { dst[dstOffset++] = val; continue; } b = buf[offset++]; val |= (b & 0x7f) << 21; if (b < 0x80) { dst[dstOffset++] = val; continue; } b = buf[offset++]; val |= (b & 0x0f) << 28; dst[dstOffset++] = val; } bufferOffset.set(offset); return dst; } export function decodeVarintInt64(src: Uint8Array, offset: IntWrapper, numValues: number): BigUint64Array { const dst = new BigUint64Array(numValues); for (let i = 0; i < dst.length; i++) { dst[i] = decodeVarintInt64Value(src, offset); } return dst; } // Source: https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/util/VarInt.java function decodeVarintInt64Value(bytes: Uint8Array, pos: IntWrapper): bigint { let value = 0n; let shift = 0; let index = pos.get(); while (index < bytes.length) { const b = bytes[index++]; value |= BigInt(b & 0x7f) << BigInt(shift); if ((b & 0x80) === 0) { break; } shift += 7; if (shift >= 64) { throw new Error("Varint too long"); } } pos.set(index); return value; } /* * Since decoding Int64 values to BigInt is more than an order of magnitude slower in the tests then using a Float64, * this decoding method limits the max size of a Long value to 53 bits */ export function decodeVarintFloat64(src: Uint8Array, offset: IntWrapper, numValues: number): Float64Array { const dst = new Float64Array(numValues); for (let i = 0; i < numValues; i++) { dst[i] = decodeVarintFloat64Value(src, offset); } return dst; } //based on https://github.com/mapbox/pbf/blob/main/index.js function decodeVarintFloat64Value(buf: Uint8Array, offset: IntWrapper): number { let val; let b; b = buf[offset.get()]; offset.increment(); val = b & 0x7f; if (b < 0x80) return val; b = buf[offset.get()]; offset.increment(); val |= (b & 0x7f) << 7; if (b < 0x80) return val; b = buf[offset.get()]; offset.increment(); val |= (b & 0x7f) << 14; if (b < 0x80) return val; b = buf[offset.get()]; offset.increment(); val |= (b & 0x7f) << 21; if (b < 0x80) return val; b = buf[offset.get()]; val |= (b & 0x0f) << 28; return decodeVarintRemainder(val, buf, offset); } function decodeVarintRemainder(l, buf, offset) { let h; let b; b = buf[offset.get()]; offset.increment(); h = (b & 0x70) >> 4; if (b < 0x80) return h * 0x100000000 + (l >>> 0); b = buf[offset.get()]; offset.increment(); h |= (b & 0x7f) << 3; if (b < 0x80) return h * 0x100000000 + (l >>> 0); b = buf[offset.get()]; offset.increment(); h |= (b & 0x7f) << 10; if (b < 0x80) return h * 0x100000000 + (l >>> 0); b = buf[offset.get()]; offset.increment(); h |= (b & 0x7f) << 17; if (b < 0x80) return h * 0x100000000 + (l >>> 0); b = buf[offset.get()]; offset.increment(); h |= (b & 0x7f) << 24; if (b < 0x80) return h * 0x100000000 + (l >>> 0); b = buf[offset.get()]; offset.increment(); h |= (b & 0x01) << 31; if (b < 0x80) return h * 0x100000000 + (l >>> 0); throw new Error("Expected varint not more than 10 bytes"); } export function decodeFastPfor( encodedBytes: Uint8Array, expectedValueCount: number, encodedByteLength: number, offset: IntWrapper, ): Uint32Array { const workspace = createFastPforWireDecodeWorkspace(encodedByteLength >>> 2); return decodeFastPforWithWorkspace(encodedBytes, expectedValueCount, encodedByteLength, offset, workspace); } export function decodeFastPforWithWorkspace( encodedBytes: Uint8Array, expectedValueCount: number, encodedByteLength: number, offset: IntWrapper, workspace: FastPforWireDecodeWorkspace, ): Uint32Array { const inputByteOffset = offset.get(); if ((encodedByteLength & 3) !== 0) { throw new Error( `FastPFOR: invalid encodedByteLength=${encodedByteLength} at offset=${inputByteOffset} (encodedBytes.length=${encodedBytes.length}; expected a multiple of 4 bytes for an int32 big-endian word stream)`, ); } const encodedWordCount = encodedByteLength >>> 2; const encodedWordBuffer = ensureFastPforWireEncodedWordsCapacity(workspace, encodedWordCount); decodeBigEndianInt32sInto(encodedBytes, inputByteOffset, encodedByteLength, encodedWordBuffer); const decodedValues = decodeFastPforInt32( encodedWordBuffer.subarray(0, encodedWordCount), expectedValueCount, workspace.decoderWorkspace, ); offset.add(encodedByteLength); return decodedValues; } export function decodeZigZagInt32Value(encoded: number): number { return (encoded >>> 1) ^ -(encoded & 1); } export function decodeZigZagInt64Value(encoded: bigint): bigint { return (encoded >> 1n) ^ -(encoded & 1n); } export function decodeZigZagFloat64Value(encoded: number): number { return encoded % 2 === 1 ? (encoded + 1) / -2 : encoded / 2; } export function decodeZigZagInt32(encodedData: Uint32Array): Int32Array { const decodedValues = new Int32Array(encodedData.length); for (let i = 0; i < encodedData.length; i++) { decodedValues[i] = decodeZigZagInt32Value(encodedData[i]); } return decodedValues; } export function decodeZigZagInt64(encodedData: BigUint64Array): BigInt64Array { const decodedValues = new BigInt64Array(encodedData.length); for (let i = 0; i < encodedData.length; i++) { decodedValues[i] = decodeZigZagInt64Value(encodedData[i]); } return decodedValues; } export function decodeZigZagFloat64(encodedData: Float64Array): void { for (let i = 0; i < encodedData.length; i++) { encodedData[i] = decodeZigZagFloat64Value(encodedData[i]); } } export function decodeUnsignedRleInt32( encodedData: Uint32Array, numRuns: number, numTotalValues?: number, ): Uint32Array { // If numTotalValues not provided, calculate from runs (nullable case) if (numTotalValues === undefined) { numTotalValues = 0; for (let i = 0; i < numRuns; i++) { numTotalValues += encodedData[i]; } } const decodedValues = new Uint32Array(numTotalValues); let offset = 0; for (let i = 0; i < numRuns; i++) { const runLength = encodedData[i]; const value = encodedData[i + numRuns]; decodedValues.fill(value, offset, offset + runLength); offset += runLength; } return decodedValues; } export function decodeUnsignedRleInt64( encodedData: BigUint64Array, numRuns: number, numTotalValues?: number, ): BigUint64Array { // If numTotalValues not provided, calculate from runs (nullable case) if (numTotalValues === undefined) { numTotalValues = 0; for (let i = 0; i < numRuns; i++) { numTotalValues += Number(encodedData[i]); } } const decodedValues = new BigUint64Array(numTotalValues); let offset = 0; for (let i = 0; i < numRuns; i++) { const runLength = Number(encodedData[i]); const value = encodedData[i + numRuns]; decodedValues.fill(value, offset, offset + runLength); offset += runLength; } return decodedValues; } export function decodeUnsignedRleFloat64( encodedData: Float64Array, numRuns: number, numTotalValues: number, ): Float64Array { const decodedValues = new Float64Array(numTotalValues); let offset = 0; for (let i = 0; i < numRuns; i++) { const runLength = encodedData[i]; const value = encodedData[i + numRuns]; decodedValues.fill(value, offset, offset + runLength); offset += runLength; } return decodedValues; } /* * In place decoding of the zigzag encoded delta values. * Inspired by https://github.com/lemire/JavaFastPFOR/blob/master/src/main/java/me/lemire/integercompression/differential/Delta.java */ export function decodeZigZagDeltaInt32(data: Uint32Array): Int32Array { const decodedValues = new Int32Array(data.length); decodedValues[0] = decodeZigZagInt32Value(data[0]); const sz0 = (data.length / 4) * 4; let i = 1; if (sz0 >= 4) { for (; i < sz0 - 4; i += 4) { const data1 = data[i]; const data2 = data[i + 1]; const data3 = data[i + 2]; const data4 = data[i + 3]; decodedValues[i] = decodeZigZagInt32Value(data1) + decodedValues[i - 1]; decodedValues[i + 1] = decodeZigZagInt32Value(data2) + decodedValues[i]; decodedValues[i + 2] = decodeZigZagInt32Value(data3) + decodedValues[i + 1]; decodedValues[i + 3] = decodeZigZagInt32Value(data4) + decodedValues[i + 2]; } } for (; i !== data.length; ++i) { decodedValues[i] = decodeZigZagInt32Value(data[i]) + decodedValues[i - 1]; } return decodedValues; } export function decodeZigZagDeltaInt64(data: BigInt64Array | BigUint64Array): BigInt64Array { const decodedValues = new BigInt64Array(data.length); decodedValues[0] = decodeZigZagInt64Value(data[0]); const sz0 = (data.length / 4) * 4; let i = 1; if (sz0 >= 4) { for (; i < sz0 - 4; i += 4) { const data1 = data[i]; const data2 = data[i + 1]; const data3 = data[i + 2]; const data4 = data[i + 3]; decodedValues[i] = decodeZigZagInt64Value(data1) + decodedValues[i - 1]; decodedValues[i + 1] = decodeZigZagInt64Value(data2) + decodedValues[i]; decodedValues[i + 2] = decodeZigZagInt64Value(data3) + decodedValues[i + 1]; decodedValues[i + 3] = decodeZigZagInt64Value(data4) + decodedValues[i + 2]; } } for (; i !== decodedValues.length; ++i) { decodedValues[i] = decodeZigZagInt64Value(data[i]) + decodedValues[i - 1]; } return decodedValues; } export function decodeZigZagDeltaFloat64(data: Float64Array) { data[0] = decodeZigZagFloat64Value(data[0]); const sz0 = (data.length / 4) * 4; let i = 1; if (sz0 >= 4) { for (; i < sz0 - 4; i += 4) { const data1 = data[i]; const data2 = data[i + 1]; const data3 = data[i + 2]; const data4 = data[i + 3]; data[i] = decodeZigZagFloat64Value(data1) + data[i - 1]; data[i + 1] = decodeZigZagFloat64Value(data2) + data[i]; data[i + 2] = decodeZigZagFloat64Value(data3) + data[i + 1]; data[i + 3] = decodeZigZagFloat64Value(data4) + data[i + 2]; } } for (; i !== data.length; ++i) { data[i] = decodeZigZagFloat64Value(data[i]) + data[i - 1]; } } export function decodeZigZagRleInt32(data: Uint32Array, numRuns: number, numTotalValues?: number): Int32Array { // If numTotalValues not provided, calculate from runs (nullable case) if (numTotalValues === undefined) { numTotalValues = 0; for (let i = 0; i < numRuns; i++) { numTotalValues += data[i]; } } const decodedValues = new Int32Array(numTotalValues); let offset = 0; for (let i = 0; i < numRuns; i++) { const runLength = data[i]; let value = data[i + numRuns]; value = decodeZigZagInt32Value(value); decodedValues.fill(value, offset, offset + runLength); offset += runLength; } return decodedValues; } export function decodeZigZagRleInt64(data: BigUint64Array, numRuns: number, numTotalValues?: number): BigInt64Array { // If numTotalValues not provided, calculate from runs (nullable case) if (numTotalValues === undefined) { numTotalValues = 0; for (let i = 0; i < numRuns; i++) { numTotalValues += Number(data[i]); } } const decodedValues = new BigInt64Array(numTotalValues); let offset = 0; for (let i = 0; i < numRuns; i++) { const runLength = Number(data[i]); let value = data[i + numRuns]; value = decodeZigZagInt64Value(value); decodedValues.fill(value, offset, offset + runLength); offset += runLength; } return decodedValues; } export function decodeZigZagRleFloat64(data: Float64Array, numRuns: number, numTotalValues: number): Float64Array { const decodedValues = new Float64Array(numTotalValues); let offset = 0; for (let i = 0; i < numRuns; i++) { const runLength = data[i]; let value = data[i + numRuns]; value = decodeZigZagFloat64Value(value); decodedValues.fill(value, offset, offset + runLength); offset += runLength; } return decodedValues; } /* * Inspired by https://github.com/lemire/JavaFastPFOR/blob/master/src/main/java/me/lemire/integercompression/differential/Delta.java */ export function fastInverseDelta(data: Uint32Array | Int32Array) { const sz0 = (data.length / 4) * 4; let i = 1; if (sz0 >= 4) { for (let a = data[0]; i < sz0 - 4; i += 4) { a = data[i] += a; a = data[i + 1] += a; a = data[i + 2] += a; a = data[i + 3] += a; } } while (i !== data.length) { data[i] += data[i - 1]; ++i; } } export function inverseDelta(data: Uint32Array) { let prevValue = 0; for (let i = 0; i < data.length; i++) { data[i] += prevValue; prevValue = data[i]; } } /* * In place decoding of the zigzag delta encoded Vec2. * Inspired by https://github.com/lemire/JavaFastPFOR/blob/master/src/main/java/me/lemire/integercompression/differential/Delta.java */ export function decodeComponentwiseDeltaVec2(data: Uint32Array): Int32Array { if (data.length < 2) return new Int32Array(data); const decodedData = new Int32Array(data.length); decodedData[0] = decodeZigZagInt32Value(data[0]); decodedData[1] = decodeZigZagInt32Value(data[1]); const sz0 = (data.length / 4) * 4; let i = 2; if (sz0 >= 4) { for (; i < sz0 - 4; i += 4) { const x1 = data[i]; const y1 = data[i + 1]; const x2 = data[i + 2]; const y2 = data[i + 3]; decodedData[i] = decodeZigZagInt32Value(x1) + decodedData[i - 2]; decodedData[i + 1] = decodeZigZagInt32Value(y1) + decodedData[i - 1]; decodedData[i + 2] = decodeZigZagInt32Value(x2) + decodedData[i]; decodedData[i + 3] = decodeZigZagInt32Value(y2) + decodedData[i + 1]; } } for (; i !== data.length; i += 2) { decodedData[i] = decodeZigZagInt32Value(data[i]) + decodedData[i - 2]; decodedData[i + 1] = decodeZigZagInt32Value(data[i + 1]) + decodedData[i - 1]; } return decodedData; } export function decodeComponentwiseDeltaVec2Scaled( data: Uint32Array, scale: number, min: number, max: number, ): Int32Array { if (data.length < 2) return new Int32Array(data); const decodedData = new Int32Array(data.length); let previousVertexX = decodeZigZagInt32Value(data[0]); let previousVertexY = decodeZigZagInt32Value(data[1]); decodedData[0] = clamp(Math.round(previousVertexX * scale), min, max); decodedData[1] = clamp(Math.round(previousVertexY * scale), min, max); const sz0 = data.length / 16; let i = 2; if (sz0 >= 4) { for (; i < sz0 - 4; i += 4) { const x1 = data[i]; const y1 = data[i + 1]; const currentVertexX = decodeZigZagInt32Value(x1) + previousVertexX; const currentVertexY = decodeZigZagInt32Value(y1) + previousVertexY; decodedData[i] = clamp(Math.round(currentVertexX * scale), min, max); decodedData[i + 1] = clamp(Math.round(currentVertexY * scale), min, max); const x2 = data[i + 2]; const y2 = data[i + 3]; previousVertexX = decodeZigZagInt32Value(x2) + currentVertexX; previousVertexY = decodeZigZagInt32Value(y2) + currentVertexY; decodedData[i + 2] = clamp(Math.round(previousVertexX * scale), min, max); decodedData[i + 3] = clamp(Math.round(previousVertexY * scale), min, max); } } for (; i !== data.length; i += 2) { previousVertexX += decodeZigZagInt32Value(data[i]); previousVertexY += decodeZigZagInt32Value(data[i + 1]); decodedData[i] = clamp(Math.round(previousVertexX * scale), min, max); decodedData[i + 1] = clamp(Math.round(previousVertexY * scale), min, max); } return decodedData; } function clamp(n: number, min: number, max: number): number { return Math.min(max, Math.max(min, n)); } /* Transform data to allow util access ------------------------------------------------------------------------ */ export function decodeZigZagDeltaOfDeltaInt32(data: Uint32Array): Uint32Array { const decodedData = new Int32Array(data.length + 1); decodedData[0] = 0; decodedData[1] = decodeZigZagInt32Value(data[0]); let deltaSum = decodedData[1]; for (let i = 2; i !== decodedData.length; ++i) { const zigZagValue = data[i - 1]; const delta = decodeZigZagInt32Value(zigZagValue); deltaSum += delta; decodedData[i] = decodedData[i - 1] + deltaSum; } return new Uint32Array(decodedData); } export function decodeZigZagRleDeltaInt32(data: Uint32Array, numRuns: number, numTotalValues: number): Int32Array { const decodedValues = new Int32Array(numTotalValues + 1); decodedValues[0] = 0; let offset = 1; let previousValue = decodedValues[0]; for (let i = 0; i < numRuns; i++) { const runLength = data[i]; let value = data[i + numRuns]; value = decodeZigZagInt32Value(value); for (let j = offset; j < offset + runLength; j++) { decodedValues[j] = value + previousValue; previousValue = decodedValues[j]; } offset += runLength; } return decodedValues; } export function decodeRleDeltaInt32(data: Uint32Array, numRuns: number, numTotalValues: number): Uint32Array { const decodedValues = new Uint32Array(numTotalValues + 1); decodedValues[0] = 0; let offset = 1; let previousValue = decodedValues[0]; for (let i = 0; i < numRuns; i++) { const runLength = data[i]; const value = data[i + numRuns]; for (let j = offset; j < offset + runLength; j++) { decodedValues[j] = value + previousValue; previousValue = decodedValues[j]; } offset += runLength; } return decodedValues; } /** * Decode Delta-RLE with multiple runs by fully reconstructing values. * * @param data RLE encoded data: [run1, run2, ..., value1, value2, ...] * @param numRuns Number of runs in the RLE encoding * @param numValues Total number of values to reconstruct * @returns Reconstructed values with deltas applied */ export function decodeDeltaRleInt32(data: Uint32Array, numRuns: number, numValues: number): Int32Array { const result = new Int32Array(numValues); let outPos = 0; let previousValue = 0; for (let i = 0; i < numRuns; i++) { const runLength = data[i]; const zigZagDelta = data[i + numRuns]; const delta = decodeZigZagInt32Value(zigZagDelta); for (let j = 0; j < runLength; j++) { previousValue += delta; result[outPos++] = previousValue; } } return result; } /** * Decode Delta-RLE with multiple runs for 64-bit integers. */ export function decodeDeltaRleInt64(data: BigUint64Array, numRuns: number, numValues: number): BigInt64Array { const result = new BigInt64Array(numValues); let outPos = 0; let previousValue = 0n; for (let i = 0; i < numRuns; i++) { const runLength = Number(data[i]); const zigZagDelta = data[i + numRuns]; const delta = decodeZigZagInt64Value(zigZagDelta); for (let j = 0; j < runLength; j++) { previousValue += delta; result[outPos++] = previousValue; } } return result; } export function decodeUnsignedZigZagDeltaInt32(data: Uint32Array): Uint32Array { const decodedValues = new Uint32Array(data.length); decodedValues[0] = decodeZigZagInt32Value(data[0]) >>> 0; for (let i = 1; i < data.length; i++) { decodedValues[i] = (decodedValues[i - 1] + decodeZigZagInt32Value(data[i])) >>> 0; } return decodedValues; } export function decodeUnsignedZigZagDeltaInt64(data: BigUint64Array): BigUint64Array { const decodedValues = new BigUint64Array(data.length); decodedValues[0] = BigInt.asUintN(64, decodeZigZagInt64Value(data[0])); for (let i = 1; i < data.length; i++) { decodedValues[i] = BigInt.asUintN(64, decodedValues[i - 1] + decodeZigZagInt64Value(data[i])); } return decodedValues; } export function decodeUnsignedComponentwiseDeltaVec2(data: Uint32Array): Uint32Array { if (data.length < 2) { return new Uint32Array(data); } const decodedData = new Uint32Array(data.length); decodedData[0] = decodeZigZagInt32Value(data[0]) >>> 0; decodedData[1] = decodeZigZagInt32Value(data[1]) >>> 0; for (let i = 2; i < data.length; i += 2) { decodedData[i] = (decodedData[i - 2] + decodeZigZagInt32Value(data[i])) >>> 0; decodedData[i + 1] = (decodedData[i - 1] + decodeZigZagInt32Value(data[i + 1])) >>> 0; } return decodedData; } export function decodeUnsignedComponentwiseDeltaVec2Scaled( data: Uint32Array, scale: number, min: number, max: number, ): Uint32Array { const scaledValues = decodeComponentwiseDeltaVec2Scaled(data, scale, min, max); return new Uint32Array(scaledValues); } export function decodeUnsignedConstRleInt32(data: Int32Array | Uint32Array): number { return data[1]; } export function decodeZigZagConstRleInt32(data: Int32Array | Uint32Array): number { return decodeZigZagInt32Value(data[1]); } export function decodeZigZagSequenceRleInt32(data: Int32Array | Uint32Array): [baseValue: number, delta: number] { /* base value and delta value are equal */ if (data.length === 2) { const value = decodeZigZagInt32Value(data[1]); return [value, value]; } /* base value and delta value are not equal -> 2 runs and 2 values*/ const base = decodeZigZagInt32Value(data[2]); const delta = decodeZigZagInt32Value(data[3]); return [base, delta]; } export function decodeUnsignedConstRleInt64(data: BigInt64Array | BigUint64Array): bigint { return data[1]; } export function decodeZigZagConstRleInt64(data: BigInt64Array | BigUint64Array): bigint { return decodeZigZagInt64Value(data[1]); } export function decodeZigZagSequenceRleInt64(data: BigInt64Array | BigUint64Array): [baseValue: bigint, delta: bigint] { /* base value and delta value are equal */ if (data.length === 2) { const value = decodeZigZagInt64Value(data[1]); return [value, value]; } /* base value and delta value are not equal -> 2 runs and 2 values*/ const base = decodeZigZagInt64Value(data[2]); const delta = decodeZigZagInt64Value(data[3]); return [base, delta]; } ================================================ FILE: ts/src/decoding/integerStreamDecoder.spec.ts ================================================ import { describe, expect, it } from "vitest"; import { decodeSignedInt32Stream, decodeSignedInt64AsFloat64Stream, decodeSignedInt64Stream, decodeSignedConstInt32Stream, decodeSignedConstInt64Stream, decodeUnsignedInt32Stream, decodeUnsignedConstInt32Stream, decodeUnsignedConstInt64Stream, decodeUnsignedInt64AsFloat64Stream, decodeUnsignedInt64Stream, getVectorType, } from "./integerStreamDecoder"; import { LogicalLevelTechnique } from "../metadata/tile/logicalLevelTechnique"; import { PhysicalLevelTechnique } from "../metadata/tile/physicalLevelTechnique"; import { VectorType } from "../vector/vectorType"; import IntWrapper from "./intWrapper"; import BitVector from "../vector/flat/bitVector"; import { createRleMetadata, createStreamMetadata } from "./decodingTestUtils"; import { encodeFloat64, encodeSignedInt32Stream, encodeInt64SignedDelta, encodeInt64SignedDeltaRle, encodeInt64SignedNone, encodeInt64SignedRle, encodeInt64UnsignedNone, encodeUnsignedInt32Stream, } from "../encoding/integerStreamEncoder"; import { encodeDeltaRleInt32, encodeVarintFloat64, encodeVarintInt64, encodeZigZagInt32Value, encodeZigZagInt64Value, } from "../encoding/integerEncodingUtils"; describe("getVectorType", () => { it("should return FLAT for RLE with 0 runs", () => { const metadata = createRleMetadata(LogicalLevelTechnique.RLE, LogicalLevelTechnique.RLE, 0, 0); const result = getVectorType(metadata, 0, new Uint8Array(), new IntWrapper(0)); expect(result).toBe(VectorType.FLAT); }); it("should return CONST for single run RLE", () => { const metadata = createRleMetadata(LogicalLevelTechnique.RLE, LogicalLevelTechnique.RLE, 1, 0); const result = getVectorType(metadata, 0, new Uint8Array(), new IntWrapper(0)); expect(result).toBe(VectorType.CONST); }); it("should return FLAT for NONE with 0 runs", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, 0); const result = getVectorType(metadata, 0, new Uint8Array(), new IntWrapper(0)); expect(result).toBe(VectorType.FLAT); }); it("should return CONST for NONE with single run", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, 1); const result = getVectorType(metadata, 0, new Uint8Array(), new IntWrapper(0)); expect(result).toBe(VectorType.CONST); }); it("should return FLAT for features and values mismatch", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.DELTA, LogicalLevelTechnique.RLE, 1); const result = getVectorType(metadata, 2, new Uint8Array(), new IntWrapper(0)); expect(result).toBe(VectorType.FLAT); }); it("should return SEQUENCE for single RLE run", () => { const metadata = createRleMetadata(LogicalLevelTechnique.DELTA, LogicalLevelTechnique.RLE, 1, 1); const result = getVectorType(metadata, 1, new Uint8Array(), new IntWrapper(0)); expect(result).toBe(VectorType.SEQUENCE); }); it("should return SEQUENCE for RLE run with 2 runs", () => { const metadata = createRleMetadata(LogicalLevelTechnique.DELTA, LogicalLevelTechnique.RLE, 2, 5); const twoRunUnitDeltaVarintPayload = new Uint8Array([1, 4, 2, 2]); // Can't achieve this array using the encoding method... const result = getVectorType(metadata, 5, twoRunUnitDeltaVarintPayload, new IntWrapper(0)); expect(result).toBe(VectorType.SEQUENCE); }); it("should probe 64-bit varints without throwing for large DELTA+RLE base values", () => { const metadata = createRleMetadata(LogicalLevelTechnique.DELTA, LogicalLevelTechnique.RLE, 2, 4); const data = encodeInt64SignedDeltaRle([ [1, 9_234_567_890n], [3, 0n], ]); const result = getVectorType(metadata, 4, data, new IntWrapper(0), "int64"); expect(result).toBe(VectorType.FLAT); }); it("should detect SEQUENCE for DELTA+RLE direct int32 payloads with unit deltas", () => { const unitDeltaEncodedValue = encodeZigZagInt32Value(1); const twoRunUnitDeltaWords = new Uint32Array([1, 4, unitDeltaEncodedValue, unitDeltaEncodedValue]); const twoRunUnitDeltaPayload = new Uint8Array(twoRunUnitDeltaWords.buffer.slice(0)); const metadata = { ...createRleMetadata(LogicalLevelTechnique.DELTA, LogicalLevelTechnique.RLE, 2, 5), physicalLevelTechnique: PhysicalLevelTechnique.NONE, byteLength: twoRunUnitDeltaPayload.byteLength, }; const result = getVectorType(metadata, 5, twoRunUnitDeltaPayload, new IntWrapper(0)); expect(result).toBe(VectorType.SEQUENCE); }); it("should return FLAT for DELTA+RLE direct int32 payloads with non-unit deltas", () => { const increasingOddValues = new Int32Array([1, 3, 5, 7, 9]); const { data: encodedWords, runs: deltaRleRunCount } = encodeDeltaRleInt32(increasingOddValues); const twoRunMixedDeltaPayload = new Uint8Array(encodedWords.buffer.slice(0)); const metadata = { ...createRleMetadata(LogicalLevelTechnique.DELTA, LogicalLevelTechnique.RLE, deltaRleRunCount, 5), physicalLevelTechnique: PhysicalLevelTechnique.NONE, byteLength: twoRunMixedDeltaPayload.byteLength, }; const result = getVectorType(metadata, 5, twoRunMixedDeltaPayload, new IntWrapper(0)); expect(result).toBe(VectorType.FLAT); }); }); describe("decodeUnsignedInt32Stream", () => { it("should decode with PhysicalLevelTechnique.NONE", () => { const expectedValues = new Uint32Array([10, 20, 30]); const metadata = createStreamMetadata(LogicalLevelTechnique.NONE); const data = encodeUnsignedInt32Stream(expectedValues, metadata); const result = decodeUnsignedInt32Stream(data, new IntWrapper(0), metadata); expect(result).toEqual(expectedValues); }); it("should decode MORTON", () => { const expectedValues = new Uint32Array([10, 15, 18, 20]); const metadata = createStreamMetadata(LogicalLevelTechnique.MORTON, LogicalLevelTechnique.NONE, 4); const data = encodeUnsignedInt32Stream(expectedValues, metadata); const result = decodeUnsignedInt32Stream(data, new IntWrapper(0), metadata); expect(result).toEqual(expectedValues); }); it("should decode nullable MORTON fully populated", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.MORTON, LogicalLevelTechnique.NONE, 4); const expectedValues = new Uint32Array([10, 15, 18, 20]); const bitVector = new BitVector(new Uint8Array([0b1111]), 4); const data = encodeUnsignedInt32Stream(expectedValues, metadata, bitVector); const offset = new IntWrapper(0); const result = decodeUnsignedInt32Stream(data, offset, metadata, undefined, bitVector); expect(result).toEqual(expectedValues); }); it("should decode nullable MORTON null values", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.MORTON, LogicalLevelTechnique.NONE, 3); const expectedValues = new Uint32Array([10, 0, 15, 0, 18]); const bitVector = new BitVector(new Uint8Array([0b10101]), 5); const data = encodeUnsignedInt32Stream(expectedValues, metadata, bitVector); const result = decodeUnsignedInt32Stream(data, new IntWrapper(0), metadata, undefined, bitVector); expect(result).toEqual(expectedValues); }); it("should decode DELTA with RLE", () => { const expectedValues = new Uint32Array([10, 12, 14, 15, 16]); const metadata = createRleMetadata( LogicalLevelTechnique.DELTA, LogicalLevelTechnique.RLE, 3, expectedValues.length, ); const data = encodeUnsignedInt32Stream(expectedValues, metadata); const result = decodeUnsignedInt32Stream(data, new IntWrapper(0), metadata); expect(result).toEqual(expectedValues); }); }); describe("decodeSignedInt32Stream", () => { it("should decode NONE signed with Int32", () => { const expectedValues = new Int32Array([2, -4, 6, -8]); const metadata = createStreamMetadata( LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, expectedValues.length, ); const data = encodeSignedInt32Stream(expectedValues, metadata); const result = decodeSignedInt32Stream(data, new IntWrapper(0), metadata); expect(result).toEqual(expectedValues); }); it("should decode nullable NONE signed Int32 partially populated", () => { const expectedValues = new Int32Array([0, 15, 0, 20]); const metadata = createStreamMetadata(LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, 2); const bitVector = new BitVector(new Uint8Array([0b1010]), 4); const data = encodeSignedInt32Stream(expectedValues, metadata, bitVector); const result = decodeSignedInt32Stream(data, new IntWrapper(0), metadata, undefined, bitVector); expect(result).toEqual(new Int32Array([0, 15, 0, 20])); }); it("should decode DELTA signed with Int32", () => { const expectedValues = new Int32Array([10, 12, 14, 16]); const metadata = createStreamMetadata( LogicalLevelTechnique.DELTA, LogicalLevelTechnique.NONE, expectedValues.length, ); const data = encodeSignedInt32Stream(expectedValues, metadata); const result = decodeSignedInt32Stream(data, new IntWrapper(0), metadata); expect(result).toEqual(expectedValues); }); it("should decode nullable DELTA signed Int32 with null values", () => { const logicalValueCount = 5; const physicalValueCount = 3; const metadata = createStreamMetadata( LogicalLevelTechnique.DELTA, LogicalLevelTechnique.NONE, physicalValueCount, ); const expectedValues = new Int32Array([0, 2, 0, 4, 6]); const bitVector = new BitVector(new Uint8Array([0b00011010]), logicalValueCount); const data = encodeSignedInt32Stream(expectedValues, metadata, bitVector); const result = decodeSignedInt32Stream(data, new IntWrapper(0), metadata, undefined, bitVector); expect(result).toEqual(expectedValues); }); it("should decode Componentwise Delta with Int32", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.COMPONENTWISE_DELTA, LogicalLevelTechnique.NONE, 4); const expectedValues = new Int32Array([10, 20, 11, 21]); const data = encodeSignedInt32Stream(expectedValues, metadata); const result = decodeSignedInt32Stream(data, new IntWrapper(0), metadata); expect(result).toEqual(expectedValues); }); it("should decode Componentwise Delta Scaled with Int32", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.COMPONENTWISE_DELTA, LogicalLevelTechnique.NONE, 4); const expectedValues = new Int32Array([100, 200, 110, 220]); const scalingData = { extent: 4096, min: 0, max: 4096, scale: 2.0 }; const data = encodeSignedInt32Stream(expectedValues, metadata, undefined, scalingData); const result = decodeSignedInt32Stream(data, new IntWrapper(0), metadata, scalingData); expect(result).toEqual(expectedValues); }); it("should decode RLE signed with Int32", () => { const expectedValues = new Int32Array([100, 100, 100, -50, -50]); const runs = 2; const metadata = createRleMetadata( LogicalLevelTechnique.RLE, LogicalLevelTechnique.NONE, runs, expectedValues.length, ); const data = encodeSignedInt32Stream(expectedValues, metadata); const result = decodeSignedInt32Stream(data, new IntWrapper(0), metadata); expect(result).toEqual(expectedValues); }); it("should decode nullable RLE Int32 partially populated", () => { let metadata = createStreamMetadata(LogicalLevelTechnique.RLE, LogicalLevelTechnique.NONE, 2); const expectedValues = new Uint32Array([0, 15, 0, 20]); const bitVector = new BitVector(new Uint8Array([0b1010]), 4); const data = encodeUnsignedInt32Stream(expectedValues, metadata, bitVector); metadata = createRleMetadata(LogicalLevelTechnique.RLE, LogicalLevelTechnique.NONE, 2, 2); const result = decodeUnsignedInt32Stream(data, new IntWrapper(0), metadata, undefined, bitVector); expect(result).toEqual(new Uint32Array([0, 15, 0, 20])); }); }); describe("decodeSignedConstInt32Stream", () => { it("should decode signed const Int32", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, 1); const data = encodeSignedInt32Stream(new Int32Array([-8]), metadata); const result = decodeSignedConstInt32Stream(data, new IntWrapper(0), metadata); expect(result).toBe(-8); }); }); describe("decodeUnsignedConstInt32Stream", () => { it("should decode unsigned const Int32", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, 1); const data = encodeUnsignedInt32Stream(new Uint32Array([0xffffffff]), metadata); const result = decodeUnsignedConstInt32Stream(data, new IntWrapper(0), metadata); expect(result).toBe(0xffffffff); }); it("should throw for unsupported technique", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.PDE, LogicalLevelTechnique.NONE, 3); const offset = new IntWrapper(0); const bitVector = new BitVector(new Uint8Array([0b00000111]), 3); expect(() => decodeUnsignedInt32Stream(new Uint8Array([]), offset, metadata, undefined, bitVector)).toThrow( "The specified Logical level technique is not supported", ); }); }); describe("decodeInt64AsFloat64Stream", () => { it("should decode NONE unsigned", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.NONE); const expectedValues = new Float64Array([1, 2, 3]); const encodedValues = encodeFloat64(new Float64Array(expectedValues), metadata, false); const data = encodeVarintFloat64(encodedValues); const result = decodeUnsignedInt64AsFloat64Stream(data, new IntWrapper(0), metadata); expect(result).toEqual(expectedValues); }); it("should decode NONE signed", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.NONE); const expectedValues = new Float64Array([2, 5, 3]); const encodedValues = encodeFloat64(new Float64Array(expectedValues), metadata, true); const data = encodeVarintFloat64(encodedValues); const result = decodeSignedInt64AsFloat64Stream(data, new IntWrapper(0), metadata); expect(result).toEqual(expectedValues); }); it("should decode RLE unsigned", () => { const expectedValues = new Float64Array([10, 10, 10, 20, 20]); const runs = 2; const metadata = createRleMetadata( LogicalLevelTechnique.RLE, LogicalLevelTechnique.NONE, runs, expectedValues.length, ); const encodedValues = encodeFloat64(new Float64Array(expectedValues), metadata, false); const data = encodeVarintFloat64(encodedValues); const result = decodeUnsignedInt64AsFloat64Stream(data, new IntWrapper(0), metadata); expect(result).toEqual(expectedValues); }); it("should decode RLE signed", () => { const expectedValues = new Float64Array([10, 10, 10, 20, 20]); const runs = 2; const metadata = createRleMetadata( LogicalLevelTechnique.RLE, LogicalLevelTechnique.NONE, runs, expectedValues.length, ); const encodedValues = encodeFloat64(new Float64Array(expectedValues), metadata, true); const data = encodeVarintFloat64(encodedValues); const result = decodeSignedInt64AsFloat64Stream(data, new IntWrapper(0), metadata); expect(result).toEqual(expectedValues); }); it("should decode DELTA without RLE", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.DELTA); const expectedValues = new Float64Array([2, 4, 6]); const encodedValues = encodeFloat64(new Float64Array(expectedValues), metadata, true); const data = encodeVarintFloat64(encodedValues); const result = decodeSignedInt64AsFloat64Stream(data, new IntWrapper(0), metadata); expect(result).toEqual(expectedValues); }); it("should decode DELTA with RLE", () => { const expectedValues = new Float64Array([10, 12, 14, 16, 18]); const runs = 2; const metadata = createRleMetadata( LogicalLevelTechnique.DELTA, LogicalLevelTechnique.RLE, runs, expectedValues.length, ); const encodedValues = encodeFloat64(new Float64Array(expectedValues), metadata, true); const data = encodeVarintFloat64(encodedValues); const result = decodeSignedInt64AsFloat64Stream(data, new IntWrapper(0), metadata); expect(result).toEqual(new Float64Array([10, 12, 14, 16, 18])); }); it("should throw for unsupported technique", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.MORTON); const values = new Uint8Array(new Float64Array([1, 2, 3]).buffer); expect(() => decodeSignedInt64AsFloat64Stream(values, new IntWrapper(0), metadata)).toThrow( "The specified Logical level technique is not supported: MORTON", ); }); }); describe("decodeInt64Stream", () => { describe("unsigned DELTA with RLE", () => { it("should decode unsigned DELTA with RLE", () => { const expectedValues = new BigUint64Array([10n, 12n, 14n, 15n, 16n]); const metadata = createRleMetadata( LogicalLevelTechnique.DELTA, LogicalLevelTechnique.RLE, 3, expectedValues.length, ); const encodedValues = new BigUint64Array([ 1n, 2n, 2n, encodeZigZagInt64Value(10n), encodeZigZagInt64Value(2n), encodeZigZagInt64Value(1n), ]); const data = encodeVarintInt64(encodedValues); const offset = new IntWrapper(0); const result = decodeUnsignedInt64Stream(data, offset, metadata); expect(result).toEqual(expectedValues); }); }); describe("DELTA with RLE", () => { it("should decode DELTA with RLE", () => { const numRleValues = 5; const runs = 3; const metadata = createRleMetadata( LogicalLevelTechnique.DELTA, LogicalLevelTechnique.RLE, runs, numRleValues, ); const expectedValues = new BigInt64Array([10n, 12n, 14n, 15n, 16n]); const data = encodeInt64SignedDeltaRle([ [1, 10n], [2, 2n], [2, 1n], ]); const offset = new IntWrapper(0); const result = decodeSignedInt64Stream(data, offset, metadata); expect(result).toEqual(expectedValues); }); it("should decode DELTA with RLE with all non-null values", () => { const numRleValues = 5; const runs = 3; const metadata = createRleMetadata( LogicalLevelTechnique.DELTA, LogicalLevelTechnique.RLE, runs, numRleValues, ); const expectedValues = new BigInt64Array([10n, 12n, 14n, 15n, 16n]); const data = encodeInt64SignedDeltaRle([ [1, 10n], [2, 2n], [2, 1n], ]); const offset = new IntWrapper(0); const bitVector = new BitVector(new Uint8Array([0b00011111]), 5); const result = decodeSignedInt64Stream(data, offset, metadata, bitVector); expect(result).toEqual(expectedValues); }); it("should decode DELTA with RLE with null values", () => { const numRleValues = 3; const runs = 2; const metadata = createRleMetadata( LogicalLevelTechnique.DELTA, LogicalLevelTechnique.RLE, runs, numRleValues, ); const expectedValues = new BigInt64Array([10n, 0n, 12n, 0n, 14n]); const data = encodeInt64SignedDeltaRle([ [1, 10n], [2, 2n], ]); const offset = new IntWrapper(0); const bitVector = new BitVector(new Uint8Array([0b00010101]), 5); const result = decodeSignedInt64Stream(data, offset, metadata, bitVector); expect(result).toEqual(expectedValues); }); }); describe("DELTA without RLE", () => { it("should decode DELTA without RLE", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.DELTA); const expectedValues = new BigInt64Array([2n, 4n, 6n]); const data = encodeInt64SignedDelta(expectedValues); const offset = new IntWrapper(0); const result = decodeSignedInt64Stream(data, offset, metadata); expect(result).toEqual(expectedValues); }); it("should decode DELTA without RLE with all non-null values", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.DELTA); const expectedValues = new BigInt64Array([2n, 4n, 6n]); const data = encodeInt64SignedDelta(expectedValues); const offset = new IntWrapper(0); const bitVector = new BitVector(new Uint8Array([0b00000111]), 3); const result = decodeSignedInt64Stream(data, offset, metadata, bitVector); expect(result).toEqual(expectedValues); }); it("should decode DELTA without RLE with null values", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.DELTA, LogicalLevelTechnique.NONE, 5); const expectedValues = new BigInt64Array([0n, 2n, 0n, 4n, 6n]); const nonNullValues = new BigInt64Array([2n, 4n, 6n]); const data = encodeInt64SignedDelta(nonNullValues); const offset = new IntWrapper(0); const bitVector = new BitVector(new Uint8Array([0b00011010]), 5); const result = decodeSignedInt64Stream(data, offset, metadata, bitVector); expect(result).toEqual(expectedValues); }); }); describe("RLE", () => { it("should decode RLE", () => { const numRleValues = 5; const runs = 2; const metadata = createRleMetadata( LogicalLevelTechnique.RLE, LogicalLevelTechnique.NONE, runs, numRleValues, ); const expectedValues = new BigInt64Array([100n, 100n, 100n, -50n, -50n]); const data = encodeInt64SignedRle([ [3, 100n], [2, -50n], ]); const offset = new IntWrapper(0); const result = decodeSignedInt64Stream(data, offset, metadata); expect(result).toEqual(expectedValues); }); it("should decode RLE with all non-null values", () => { const numRleValues = 5; const runs = 2; const metadata = createRleMetadata( LogicalLevelTechnique.RLE, LogicalLevelTechnique.NONE, runs, numRleValues, ); const expectedValues = new BigInt64Array([100n, 100n, 100n, -50n, -50n]); const data = encodeInt64SignedRle([ [3, 100n], [2, -50n], ]); const offset = new IntWrapper(0); const bitVector = new BitVector(new Uint8Array([0b00011111]), 5); const result = decodeSignedInt64Stream(data, offset, metadata, bitVector); expect(result).toEqual(expectedValues); }); it("should decode RLE with null values", () => { const numRleValues = 3; const runs = 2; const metadata = createRleMetadata( LogicalLevelTechnique.RLE, LogicalLevelTechnique.NONE, runs, numRleValues, ); const expectedValues = new BigInt64Array([100n, 0n, 100n, 0n, -50n]); const data = encodeInt64SignedRle([ [2, 100n], [1, -50n], ]); const offset = new IntWrapper(0); const bitVector = new BitVector(new Uint8Array([0b00010101]), 5); const result = decodeSignedInt64Stream(data, offset, metadata, bitVector); expect(result).toEqual(expectedValues); }); }); describe("NONE", () => { it("should decode NONE signed", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.NONE); const expectedValues = new BigInt64Array([2n, -4n, 6n]); const data = encodeInt64SignedNone(expectedValues); const offset = new IntWrapper(0); const result = decodeSignedInt64Stream(data, offset, metadata); expect(result).toEqual(expectedValues); }); it("should decode NONE signed min int64", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, 1); const expectedValues = new BigInt64Array([-(2n ** 63n)]); const data = encodeInt64SignedNone(expectedValues); const offset = new IntWrapper(0); const result = decodeSignedInt64Stream(data, offset, metadata); expect(result).toEqual(expectedValues); }); it("should decode NONE unsigned", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.NONE); const expectedValues = new BigUint64Array([1n, 2n, 3n]); const data = encodeInt64UnsignedNone(new BigInt64Array(expectedValues)); const offset = new IntWrapper(0); const result = decodeUnsignedInt64Stream(data, offset, metadata); expect(result).toEqual(expectedValues); }); it("should decode signed const Int64", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, 1); const data = encodeInt64SignedNone(new BigInt64Array([-8n])); const result = decodeSignedConstInt64Stream(data, new IntWrapper(0), metadata); expect(result).toBe(-8n); }); it("should decode unsigned const Int64", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, 1); const data = encodeInt64UnsignedNone(new BigInt64Array([0xffffffffffffffffn])); const result = decodeUnsignedConstInt64Stream(data, new IntWrapper(0), metadata); expect(result).toBe(0xffffffffffffffffn); }); it("should decode NONE signed with all non-null values", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.NONE); const expectedValues = new BigInt64Array([2n, -4n, 6n]); const data = encodeInt64SignedNone(expectedValues); const offset = new IntWrapper(0); const bitVector = new BitVector(new Uint8Array([0b00000111]), 3); const result = decodeSignedInt64Stream(data, offset, metadata, bitVector); expect(result).toEqual(expectedValues); }); it("should decode NONE signed with null values", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, 5); const expectedValues = new BigInt64Array([2n, 0n, -4n, 0n, 6n]); const nonNullValues = new BigInt64Array([2n, -4n, 6n]); const data = encodeInt64SignedNone(nonNullValues); const offset = new IntWrapper(0); const bitVector = new BitVector(new Uint8Array([0b00010101]), 5); const result = decodeSignedInt64Stream(data, offset, metadata, bitVector); expect(result).toEqual(expectedValues); }); it("should decode NONE unsigned with all non-null values", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.NONE); const expectedValues = new BigUint64Array([1n, 2n, 3n]); const data = encodeInt64UnsignedNone(new BigInt64Array(expectedValues)); const offset = new IntWrapper(0); const bitVector = new BitVector(new Uint8Array([0b00000111]), 3); const result = decodeUnsignedInt64Stream(data, offset, metadata, bitVector); expect(result).toEqual(expectedValues); }); it("should decode NONE unsigned with null values", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, 5); const expectedValues = new BigUint64Array([0n, 1n, 2n, 0n, 3n]); const nonNullValues = new BigInt64Array([1n, 2n, 3n]); const data = encodeInt64UnsignedNone(nonNullValues); const offset = new IntWrapper(0); const bitVector = new BitVector(new Uint8Array([0b00010110]), 5); const result = decodeUnsignedInt64Stream(data, offset, metadata, bitVector); expect(result).toEqual(expectedValues); }); }); describe("error handling", () => { it("should throw for unsupported technique", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.MORTON); const data = encodeInt64UnsignedNone(new BigInt64Array([1n, 2n, 3n])); const offset = new IntWrapper(0); expect(() => decodeSignedInt64Stream(data, offset, metadata)).toThrow( "The specified Logical level technique is not supported: MORTON", ); }); it("should throw for unsupported technique with nullable", () => { const metadata = createStreamMetadata(LogicalLevelTechnique.COMPONENTWISE_DELTA); const values = new BigInt64Array([1n, 2n, 3n]); const data = encodeInt64UnsignedNone(values); const offset = new IntWrapper(0); const bitVector = new BitVector(new Uint8Array([0b00000111]), 3); expect(() => decodeSignedInt64Stream(data, offset, metadata, bitVector)).toThrow(); }); }); }); ================================================ FILE: ts/src/decoding/integerStreamDecoder.ts ================================================ import { PhysicalLevelTechnique } from "../metadata/tile/physicalLevelTechnique"; import IntWrapper from "./intWrapper"; import { decodeComponentwiseDeltaVec2, decodeComponentwiseDeltaVec2Scaled, decodeDeltaRleInt32, decodeDeltaRleInt64, decodeFastPfor, decodeUnsignedComponentwiseDeltaVec2, decodeUnsignedComponentwiseDeltaVec2Scaled, decodeUnsignedConstRleInt32, decodeUnsignedConstRleInt64, decodeUnsignedRleInt32, decodeUnsignedRleInt64, decodeUnsignedRleFloat64, decodeUnsignedZigZagDeltaInt32, decodeUnsignedZigZagDeltaInt64, decodeVarintInt32, decodeVarintInt64, decodeVarintFloat64, decodeZigZagInt32, decodeZigZagInt64, decodeZigZagFloat64, decodeZigZagConstRleInt32, decodeZigZagConstRleInt64, decodeZigZagDeltaInt32, decodeZigZagDeltaInt64, decodeZigZagDeltaFloat64, decodeZigZagSequenceRleInt32, decodeZigZagSequenceRleInt64, decodeZigZagInt32Value, decodeZigZagInt64Value, fastInverseDelta, inverseDelta, decodeRleDeltaInt32, decodeZigZagDeltaOfDeltaInt32, decodeZigZagRleDeltaInt32, decodeZigZagRleInt32, decodeZigZagRleInt64, decodeZigZagRleFloat64, } from "./integerDecodingUtils"; import { LogicalLevelTechnique } from "../metadata/tile/logicalLevelTechnique"; import type { StreamMetadata, RleEncodedStreamMetadata } from "../metadata/tile/streamMetadataDecoder"; import BitVector from "../vector/flat/bitVector"; import { VectorType } from "../vector/vectorType"; import type GeometryScaling from "./geometryScaling"; import { unpackNullable } from "./unpackNullableUtils"; export function decodeSignedInt32Stream( data: Uint8Array, offset: IntWrapper, streamMetadata: StreamMetadata, scalingData?: GeometryScaling, nullabilityBuffer?: BitVector, ): Int32Array { const values = decodePhysicalLevelTechnique(data, offset, streamMetadata); return decodeSignedInt32(values, streamMetadata, scalingData, nullabilityBuffer); } export function decodeUnsignedInt32Stream( data: Uint8Array, offset: IntWrapper, streamMetadata: StreamMetadata, scalingData?: GeometryScaling, nullabilityBuffer?: BitVector, ): Uint32Array { const values = decodePhysicalLevelTechnique(data, offset, streamMetadata); return decodeUnsignedInt32(values, streamMetadata, scalingData, nullabilityBuffer); } export function decodeLengthStreamToOffsetBuffer( data: Uint8Array, offset: IntWrapper, streamMetadata: StreamMetadata, ): Uint32Array { const values = decodePhysicalLevelTechnique(data, offset, streamMetadata); return decodeLengthToOffsetBuffer(values, streamMetadata); } function decodePhysicalLevelTechnique( data: Uint8Array, offset: IntWrapper, streamMetadata: StreamMetadata, ): Uint32Array { const physicalLevelTechnique = streamMetadata.physicalLevelTechnique; switch (physicalLevelTechnique) { case PhysicalLevelTechnique.FAST_PFOR: return decodeFastPfor(data, streamMetadata.numValues, streamMetadata.byteLength, offset); case PhysicalLevelTechnique.VARINT: return decodeVarintInt32(data, offset, streamMetadata.numValues); case PhysicalLevelTechnique.NONE: { const dataOffset = offset.get(); const byteLength = streamMetadata.byteLength; offset.add(byteLength); const slice = data.subarray(dataOffset, offset.get()); return new Uint32Array(slice); } default: throw new Error(`Specified physicalLevelTechnique ${physicalLevelTechnique} is not supported (yet).`); } } export function decodeSignedConstInt32Stream( data: Uint8Array, offset: IntWrapper, streamMetadata: StreamMetadata, ): number { const values = decodePhysicalLevelTechnique(data, offset, streamMetadata); if (values.length === 1) { return decodeZigZagInt32Value(values[0]); } return decodeZigZagConstRleInt32(values); } export function decodeUnsignedConstInt32Stream( data: Uint8Array, offset: IntWrapper, streamMetadata: StreamMetadata, ): number { const values = decodePhysicalLevelTechnique(data, offset, streamMetadata); if (values.length === 1) { return values[0]; } return decodeUnsignedConstRleInt32(values); } export function decodeSequenceInt32Stream( data: Uint8Array, offset: IntWrapper, streamMetadata: StreamMetadata, ): [baseValue: number, delta: number] { const values = decodePhysicalLevelTechnique(data, offset, streamMetadata); return decodeZigZagSequenceRleInt32(values); } export function decodeSequenceInt64Stream( data: Uint8Array, offset: IntWrapper, streamMetadata: StreamMetadata, ): [baseValue: bigint, delta: bigint] { const values = decodeVarintInt64(data, offset, streamMetadata.numValues); return decodeZigZagSequenceRleInt64(values); } export function decodeSignedInt64Stream( data: Uint8Array, offset: IntWrapper, streamMetadata: StreamMetadata, nullabilityBuffer?: BitVector, ): BigInt64Array { const values = decodeVarintInt64(data, offset, streamMetadata.numValues); return decodeSignedInt64(values, streamMetadata, nullabilityBuffer); } export function decodeUnsignedInt64Stream( data: Uint8Array, offset: IntWrapper, streamMetadata: StreamMetadata, nullabilityBuffer?: BitVector, ): BigUint64Array { const values = decodeVarintInt64(data, offset, streamMetadata.numValues); return decodeUnsignedInt64(values, streamMetadata, nullabilityBuffer); } export function decodeSignedInt64AsFloat64Stream( data: Uint8Array, offset: IntWrapper, streamMetadata: StreamMetadata, ): Float64Array { const values = decodeVarintFloat64(data, offset, streamMetadata.numValues); return decodeFloat64Values(values, streamMetadata, true); } export function decodeUnsignedInt64AsFloat64Stream( data: Uint8Array, offset: IntWrapper, streamMetadata: StreamMetadata, ): Float64Array { const values = decodeVarintFloat64(data, offset, streamMetadata.numValues); return decodeFloat64Values(values, streamMetadata, false); } export function decodeSignedConstInt64Stream( data: Uint8Array, offset: IntWrapper, streamMetadata: StreamMetadata, ): bigint { const values = decodeVarintInt64(data, offset, streamMetadata.numValues); if (values.length === 1) { return decodeZigZagInt64Value(values[0]); } return decodeZigZagConstRleInt64(values); } export function decodeUnsignedConstInt64Stream( data: Uint8Array, offset: IntWrapper, streamMetadata: StreamMetadata, ): bigint { const values = decodeVarintInt64(data, offset, streamMetadata.numValues); if (values.length === 1) { return values[0]; } return decodeUnsignedConstRleInt64(values); } /** * This method decodes integer streams. * Currently the encoder uses only fixed combinations of encodings. * For performance reasons it is also uses a fixed combination of the encodings on the decoding side. * The following encodings and combinations are used: * - Morton Delta -> always sorted so not ZigZag encoding needed * - Delta -> currently always in combination with ZigZag encoding * - Rle -> in combination with ZigZag encoding if data type is signed * - Delta Rle * - Componentwise Delta -> always ZigZag encoding is used */ function decodeSignedInt32( values: Uint32Array, streamMetadata: StreamMetadata, scalingData?: GeometryScaling, nullabilityBuffer?: BitVector, ): Int32Array { let decodedValues: Int32Array; switch (streamMetadata.logicalLevelTechnique1) { case LogicalLevelTechnique.DELTA: if (streamMetadata.logicalLevelTechnique2 === LogicalLevelTechnique.RLE) { const rleMetadata = streamMetadata as RleEncodedStreamMetadata; if (!nullabilityBuffer) { return decodeDeltaRleInt32(values, rleMetadata.runs, rleMetadata.numRleValues); } values = decodeUnsignedRleInt32(values, rleMetadata.runs, rleMetadata.numRleValues); decodedValues = decodeZigZagDeltaInt32(values); } else { decodedValues = decodeZigZagDeltaInt32(values); } break; case LogicalLevelTechnique.RLE: decodedValues = decodeZigZagRleInt32( values, (streamMetadata as RleEncodedStreamMetadata).runs, (streamMetadata as RleEncodedStreamMetadata).numRleValues, ); break; case LogicalLevelTechnique.MORTON: fastInverseDelta(values); decodedValues = new Int32Array(values); break; case LogicalLevelTechnique.COMPONENTWISE_DELTA: if (scalingData && !nullabilityBuffer) { return decodeComponentwiseDeltaVec2Scaled(values, scalingData.scale, scalingData.min, scalingData.max); } decodedValues = decodeComponentwiseDeltaVec2(values); break; case LogicalLevelTechnique.NONE: decodedValues = decodeZigZagInt32(values); break; default: throw new Error( `The specified Logical level technique is not supported: ${streamMetadata.logicalLevelTechnique1}`, ); } if (nullabilityBuffer) { return unpackNullable(decodedValues, nullabilityBuffer, 0); } return decodedValues; } function decodeUnsignedInt32( values: Uint32Array, streamMetadata: StreamMetadata, scalingData?: GeometryScaling, nullabilityBuffer?: BitVector, ): Uint32Array { let decodedValues: Uint32Array; switch (streamMetadata.logicalLevelTechnique1) { case LogicalLevelTechnique.DELTA: if (streamMetadata.logicalLevelTechnique2 === LogicalLevelTechnique.RLE) { const rleMetadata = streamMetadata as RleEncodedStreamMetadata; const deltaValues = decodeUnsignedRleInt32(values, rleMetadata.runs, rleMetadata.numRleValues); decodedValues = decodeUnsignedZigZagDeltaInt32(deltaValues); } else { decodedValues = decodeUnsignedZigZagDeltaInt32(values); } break; case LogicalLevelTechnique.RLE: decodedValues = decodeUnsignedRleInt32( values, (streamMetadata as RleEncodedStreamMetadata).runs, (streamMetadata as RleEncodedStreamMetadata).numRleValues, ); break; case LogicalLevelTechnique.MORTON: fastInverseDelta(values); decodedValues = values; break; case LogicalLevelTechnique.COMPONENTWISE_DELTA: if (scalingData && !nullabilityBuffer) { decodedValues = decodeUnsignedComponentwiseDeltaVec2Scaled( values, scalingData.scale, scalingData.min, scalingData.max, ); } else { decodedValues = decodeUnsignedComponentwiseDeltaVec2(values); } break; case LogicalLevelTechnique.NONE: decodedValues = values; break; default: throw new Error( `The specified Logical level technique is not supported: ${streamMetadata.logicalLevelTechnique1}`, ); } if (nullabilityBuffer) { return unpackNullable(decodedValues, nullabilityBuffer, 0); } return decodedValues; } function decodeSignedInt64( values: BigUint64Array, streamMetadata: StreamMetadata, nullabilityBuffer?: BitVector, ): BigInt64Array { let decodedValues: BigInt64Array; switch (streamMetadata.logicalLevelTechnique1) { case LogicalLevelTechnique.DELTA: if (streamMetadata.logicalLevelTechnique2 === LogicalLevelTechnique.RLE) { const rleMetadata = streamMetadata as RleEncodedStreamMetadata; if (!nullabilityBuffer) { return decodeDeltaRleInt64(values, rleMetadata.runs, rleMetadata.numRleValues); } values = decodeUnsignedRleInt64(values, rleMetadata.runs, rleMetadata.numRleValues); decodedValues = decodeZigZagDeltaInt64(values); } else { decodedValues = decodeZigZagDeltaInt64(values); } break; case LogicalLevelTechnique.RLE: decodedValues = decodeZigZagRleInt64( values, (streamMetadata as RleEncodedStreamMetadata).runs, (streamMetadata as RleEncodedStreamMetadata).numRleValues, ); break; case LogicalLevelTechnique.NONE: decodedValues = decodeZigZagInt64(values); break; default: throw new Error( `The specified Logical level technique is not supported: ${streamMetadata.logicalLevelTechnique1}`, ); } if (nullabilityBuffer) { return unpackNullable(decodedValues, nullabilityBuffer, 0n); } return decodedValues; } function decodeUnsignedInt64( values: BigUint64Array, streamMetadata: StreamMetadata, nullabilityBuffer?: BitVector, ): BigUint64Array { let decodedValues: BigUint64Array; switch (streamMetadata.logicalLevelTechnique1) { case LogicalLevelTechnique.DELTA: if (streamMetadata.logicalLevelTechnique2 === LogicalLevelTechnique.RLE) { const rleMetadata = streamMetadata as RleEncodedStreamMetadata; const deltaValues = decodeUnsignedRleInt64(values, rleMetadata.runs, rleMetadata.numRleValues); decodedValues = decodeUnsignedZigZagDeltaInt64(deltaValues); } else { decodedValues = decodeUnsignedZigZagDeltaInt64(values); } break; case LogicalLevelTechnique.RLE: decodedValues = decodeUnsignedRleInt64( values, (streamMetadata as RleEncodedStreamMetadata).runs, (streamMetadata as RleEncodedStreamMetadata).numRleValues, ); break; case LogicalLevelTechnique.NONE: decodedValues = values; break; default: throw new Error( `The specified Logical level technique is not supported: ${streamMetadata.logicalLevelTechnique1}`, ); } if (nullabilityBuffer) { return unpackNullable(decodedValues, nullabilityBuffer, 0n); } return decodedValues; } function decodeFloat64Values(values: Float64Array, streamMetadata: StreamMetadata, isSigned: boolean): Float64Array { switch (streamMetadata.logicalLevelTechnique1) { case LogicalLevelTechnique.DELTA: if (streamMetadata.logicalLevelTechnique2 === LogicalLevelTechnique.RLE) { const rleMetadata = streamMetadata as RleEncodedStreamMetadata; values = decodeUnsignedRleFloat64(values, rleMetadata.runs, rleMetadata.numRleValues); } decodeZigZagDeltaFloat64(values); return values; case LogicalLevelTechnique.RLE: return decodeRleFloat64(values, streamMetadata as RleEncodedStreamMetadata, isSigned); case LogicalLevelTechnique.NONE: if (isSigned) { decodeZigZagFloat64(values); } return values; default: throw new Error( `The specified Logical level technique is not supported: ${streamMetadata.logicalLevelTechnique1}`, ); } } function decodeLengthToOffsetBuffer(values: Uint32Array, streamMetadata: StreamMetadata): Uint32Array { if ( streamMetadata.logicalLevelTechnique1 === LogicalLevelTechnique.DELTA && streamMetadata.logicalLevelTechnique2 === LogicalLevelTechnique.NONE ) { return decodeZigZagDeltaOfDeltaInt32(values); } if ( streamMetadata.logicalLevelTechnique1 === LogicalLevelTechnique.RLE && streamMetadata.logicalLevelTechnique2 === LogicalLevelTechnique.NONE ) { const rleMetadata = streamMetadata as RleEncodedStreamMetadata; return decodeRleDeltaInt32(values, rleMetadata.runs, rleMetadata.numRleValues); } if ( streamMetadata.logicalLevelTechnique1 === LogicalLevelTechnique.NONE && streamMetadata.logicalLevelTechnique2 === LogicalLevelTechnique.NONE ) { //TODO: use fastInverseDelta again and check what are the performance problems in zoom 14 //fastInverseDelta(values); inverseDelta(values); const offsets = new Uint32Array(streamMetadata.numValues + 1); offsets[0] = 0; offsets.set(values, 1); return offsets; } if ( streamMetadata.logicalLevelTechnique1 === LogicalLevelTechnique.DELTA && streamMetadata.logicalLevelTechnique2 === LogicalLevelTechnique.RLE ) { const rleMetadata = streamMetadata as RleEncodedStreamMetadata; const decodedValues = decodeZigZagRleDeltaInt32(values, rleMetadata.runs, rleMetadata.numRleValues); fastInverseDelta(decodedValues); return new Uint32Array(decodedValues); } throw new Error("Only delta encoding is supported for transforming length to offset streams yet."); } export function getVectorType( streamMetadata: StreamMetadata, sizeOrNullabilityBuffer: number | BitVector, data: Uint8Array, offset: IntWrapper, varintWidth: "int32" | "int64" = "int32", ): VectorType { const logicalLevelTechnique1 = streamMetadata.logicalLevelTechnique1; if (logicalLevelTechnique1 === LogicalLevelTechnique.RLE) { return (streamMetadata as RleEncodedStreamMetadata).runs === 1 ? VectorType.CONST : VectorType.FLAT; } if ( logicalLevelTechnique1 !== LogicalLevelTechnique.DELTA || streamMetadata.logicalLevelTechnique2 !== LogicalLevelTechnique.RLE ) { return streamMetadata.numValues === 1 ? VectorType.CONST : VectorType.FLAT; } const numFeatures = sizeOrNullabilityBuffer instanceof BitVector ? sizeOrNullabilityBuffer.size() : sizeOrNullabilityBuffer; const rleMetadata = streamMetadata as RleEncodedStreamMetadata; if (rleMetadata.numRleValues !== numFeatures) { return VectorType.FLAT; } // Single run is always a sequence if (rleMetadata.runs === 1) { return VectorType.SEQUENCE; } if (rleMetadata.runs !== 2) { return streamMetadata.numValues === 1 ? VectorType.CONST : VectorType.FLAT; } // Two runs can be a sequence if both deltas are equal to 1 const savedOffset = offset.get(); if (streamMetadata.physicalLevelTechnique === PhysicalLevelTechnique.VARINT) { if (isDeltaRleSequenceVarintWidth(data, offset, varintWidth)) { return VectorType.SEQUENCE; } return streamMetadata.numValues === 1 ? VectorType.CONST : VectorType.FLAT; } const byteOffset = offset.get(); const values = new Int32Array(data.buffer, data.byteOffset + byteOffset, 4); offset.set(savedOffset); // Check if both deltas are encoded 1 const zigZagOne = 2; if (values[2] === zigZagOne && values[3] === zigZagOne) { return VectorType.SEQUENCE; } return streamMetadata.numValues === 1 ? VectorType.CONST : VectorType.FLAT; } function isDeltaRleSequenceVarintWidth(data: Uint8Array, offset: IntWrapper, varintWidth: "int32" | "int64"): boolean { const peekOffset = new IntWrapper(offset.get()); if (varintWidth === "int64") { const values = decodeVarintInt64(data, peekOffset, 4); return values[2] === 2n && values[3] === 2n; } const values = decodeVarintInt32(data, peekOffset, 4); return values[2] === 2 && values[3] === 2; } function decodeRleFloat64( data: Float64Array, streamMetadata: RleEncodedStreamMetadata, isSigned: boolean, ): Float64Array { return isSigned ? decodeZigZagRleFloat64(data, streamMetadata.runs, streamMetadata.numRleValues) : decodeUnsignedRleFloat64(data, streamMetadata.runs, streamMetadata.numRleValues); } ================================================ FILE: ts/src/decoding/propertyDecoder.spec.ts ================================================ import { describe, it, expect } from "vitest"; import { decodePropertyColumn } from "./propertyDecoder"; import IntWrapper from "./intWrapper"; import type Vector from "../vector/vector"; import { ScalarType, type Column } from "../metadata/tileset/tilesetMetadata"; import { Int32FlatVector } from "../vector/flat/int32FlatVector"; import { Int64FlatVector } from "../vector/flat/int64FlatVector"; import { FloatFlatVector } from "../vector/flat/floatFlatVector"; import { DoubleFlatVector } from "../vector/flat/doubleFlatVector"; import { BooleanFlatVector } from "../vector/flat/booleanFlatVector"; import { Int32SequenceVector } from "../vector/sequence/int32SequenceVector"; import { Int64SequenceVector } from "../vector/sequence/int64SequenceVector"; import { Int32ConstVector } from "../vector/constant/int32ConstVector"; import { Int64ConstVector } from "../vector/constant/int64ConstVector"; import { StringDictionaryVector } from "../vector/dictionary/stringDictionaryVector"; import { createColumnMetadataForStruct, encodeSharedDictionary, encodeStructField } from "./decodingTestUtils"; import { concatenateBuffers } from "../encoding/encodingUtils"; import { encodeInt32NoneColumn, encodeInt32DeltaColumn, encodeInt32RleColumn, encodeInt32DeltaRleColumn, encodeUint32Column, encodeInt64NoneColumn, encodeInt64DeltaColumn, encodeInt64RleColumn, encodeInt64DeltaRleColumn, encodeInt64NullableColumn, encodeUint64Column, encodeUint64NullableColumn, encodeFloatColumn, encodeFloatNullableColumn, encodeDoubleColumn, encodeDoubleNullableColumn, encodeBooleanColumn, encodeBooleanNullableColumn, encodeInt32NullableColumn, } from "../encoding/propertyEncoder"; function createColumnMetadata(name: string, scalarType: number, nullable = false): Column { return { name: name, nullable: nullable, type: "scalarType", scalarType: { physicalType: scalarType, type: "physicalType", }, }; } describe("decodePropertyColumn - INT_32", () => { it("should decode INT_32 column with NONE encoding (signed)", () => { const expectedValues = new Int32Array([2, -4, 6]); const columnMetadata = createColumnMetadata("testColumn", ScalarType.INT_32, false); const encodedData = encodeInt32NoneColumn(expectedValues); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, expectedValues.length); expect(result).toBeInstanceOf(Int32FlatVector); const resultVec = result as Int32FlatVector; for (let i = 0; i < expectedValues.length; i++) { expect(resultVec.getValue(i)).toBe(expectedValues[i]); } }); it("should decode INT_32 column with DELTA encoding", () => { const expectedValues = new Int32Array([2, 4, 6]); const columnMetadata = createColumnMetadata("testColumn", ScalarType.INT_32, false); const encodedData = encodeInt32DeltaColumn(expectedValues); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, expectedValues.length); expect(result).toBeInstanceOf(Int32FlatVector); const resultVec = result as Int32FlatVector; for (let i = 0; i < expectedValues.length; i++) { expect(resultVec.getValue(i)).toBe(expectedValues[i]); } }); it("should decode INT_32 column with RLE encoding", () => { const expectedValues = new Int32Array([100, 100, 100, -50, -50]); const columnMetadata = createColumnMetadata("testColumn", ScalarType.INT_32, false); const encodedData = encodeInt32RleColumn([ [3, 100], [2, -50], ]); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, expectedValues.length); expect(result).toBeInstanceOf(Int32FlatVector); const resultVec = result as Int32FlatVector; for (let i = 0; i < expectedValues.length; i++) { expect(resultVec.getValue(i)).toBe(expectedValues[i]); } }); it("should decode INT_32 column with DELTA+RLE encoding", () => { const expectedValues = new Int32Array([10, 12, 14, 15, 16]); const columnMetadata = createColumnMetadata("testColumn", ScalarType.INT_32, false); const encodedData = encodeInt32DeltaRleColumn([ [1, 10], [2, 2], [2, 1], ]); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, expectedValues.length); expect(result).toBeInstanceOf(Int32FlatVector); const resultVec = result as Int32FlatVector; for (let i = 0; i < expectedValues.length; i++) { expect(resultVec.getValue(i)).toBe(expectedValues[i]); } }); it("should decode nullable INT_32 column with null values", () => { const expectedValues = [2, null, -4, null, 6]; const columnMetadata = createColumnMetadata("testColumn", ScalarType.INT_32, true); const encodedData = encodeInt32NullableColumn(expectedValues); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 2, expectedValues.length); expect(result).toBeInstanceOf(Int32FlatVector); const resultVec = result as Int32FlatVector; for (let i = 0; i < expectedValues.length; i++) { expect(resultVec.getValue(i)).toBe(expectedValues[i]); } }); it("should decode INT_32 SEQUENCE vector", () => { const numValues = 5; const value = 10; const columnMetadata = createColumnMetadata("testColumn", ScalarType.INT_32, false); const encodedData = encodeInt32DeltaRleColumn([[numValues, value]]); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, numValues); expect(result).toBeInstanceOf(Int32SequenceVector); const seqVec = result as Int32SequenceVector; expect(seqVec.getValue(0)).toBe(value); expect(seqVec.getValue(1)).toBe(value + value); expect(seqVec.getValue(2)).toBe(value + value * 2); }); it("should decode INT_32 CONST vector", () => { const numValues = 5; const constValue = 42; const columnMetadata = createColumnMetadata("testColumn", ScalarType.INT_32, false); const encodedData = encodeInt32RleColumn([[numValues, constValue]]); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, numValues); expect(result).toBeInstanceOf(Int32ConstVector); const constVec = result as Int32ConstVector; expect(constVec.getValue(0)).toBe(constValue); expect(constVec.getValue(4)).toBe(constValue); }); }); describe("decodePropertyColumn - UINT_32", () => { it("should decode UINT_32 column with NONE encoding (unsigned)", () => { const expectedValues = new Uint32Array([2, 4, 6, 100]); const columnMetadata = createColumnMetadata("testColumn", ScalarType.UINT_32, false); const encodedData = encodeUint32Column(expectedValues); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, expectedValues.length); expect(result).toBeInstanceOf(Int32FlatVector); const resultVec = result as Int32FlatVector; for (let i = 0; i < expectedValues.length; i++) { expect(resultVec.getValue(i)).toBe(expectedValues[i]); } }); it("should decode UINT_32 max value in FLAT vector", () => { const expectedValues = new Uint32Array([0xffffffff, 1]); const columnMetadata = createColumnMetadata("testColumn", ScalarType.UINT_32, false); const encodedData = encodeUint32Column(expectedValues); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, expectedValues.length); expect(result).toBeInstanceOf(Int32FlatVector); const resultVec = result as Int32FlatVector; expect(resultVec.getValue(0)).toBe(0xffffffff); expect(resultVec.getValue(1)).toBe(1); }); it("should decode UINT_32 CONST vector", () => { const expectedValue = 0xffffffff; const columnMetadata = createColumnMetadata("testColumn", ScalarType.UINT_32, false); const encodedData = encodeUint32Column(new Uint32Array([expectedValue])); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, 1); expect(result).toBeInstanceOf(Int32ConstVector); const constVec = result as Int32ConstVector; expect(constVec.getValue(0)).toBe(expectedValue); }); }); describe("decodePropertyColumn - INT_64", () => { it("should decode INT_64 column with NONE encoding (signed)", () => { const expectedValues = new BigInt64Array([2n, -4n, 6n]); const columnMetadata = createColumnMetadata("testColumn", ScalarType.INT_64, false); const encodedData = encodeInt64NoneColumn(expectedValues); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, expectedValues.length); expect(result).toBeInstanceOf(Int64FlatVector); const resultVec = result as Int64FlatVector; for (let i = 0; i < expectedValues.length; i++) { expect(resultVec.getValue(i)).toBe(expectedValues[i]); } }); it("should decode INT_64 column with DELTA encoding", () => { const expectedValues = new BigInt64Array([2n, 4n, 6n]); const columnMetadata = createColumnMetadata("testColumn", ScalarType.INT_64, false); const encodedData = encodeInt64DeltaColumn(expectedValues); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, expectedValues.length); expect(result).toBeInstanceOf(Int64FlatVector); const resultVec = result as Int64FlatVector; for (let i = 0; i < expectedValues.length; i++) { expect(resultVec.getValue(i)).toBe(expectedValues[i]); } }); it("should decode INT_64 column with RLE encoding", () => { const expectedValues = new BigInt64Array([100n, 100n, 100n, -50n, -50n]); const columnMetadata = createColumnMetadata("testColumn", ScalarType.INT_64, false); const encodedData = encodeInt64RleColumn([ [3, 100n], [2, -50n], ]); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, expectedValues.length); expect(result).toBeInstanceOf(Int64FlatVector); const resultVec = result as Int64FlatVector; for (let i = 0; i < expectedValues.length; i++) { expect(resultVec.getValue(i)).toBe(expectedValues[i]); } }); it("should decode INT_64 column with DELTA+RLE encoding", () => { const expectedValues = new BigInt64Array([10n, 12n, 14n, 15n, 16n]); const columnMetadata = createColumnMetadata("testColumn", ScalarType.INT_64, false); const encodedData = encodeInt64DeltaRleColumn([ [1, 10n], [2, 2n], [2, 1n], ]); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, expectedValues.length); expect(result).toBeInstanceOf(Int64FlatVector); const resultVec = result as Int64FlatVector; for (let i = 0; i < expectedValues.length; i++) { expect(resultVec.getValue(i)).toBe(expectedValues[i]); } }); it("should decode nullable INT_64 column with null values", () => { const expectedValues = [2n, null, -4n, null, 6n]; const columnMetadata = createColumnMetadata("testColumn", ScalarType.INT_64, true); const encodedData = encodeInt64NullableColumn(expectedValues); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 2, expectedValues.length); expect(result).toBeInstanceOf(Int64FlatVector); const resultVec = result as Int64FlatVector; for (let i = 0; i < expectedValues.length; i++) { expect(resultVec.getValue(i)).toBe(expectedValues[i]); } }); it("should decode INT_64 SEQUENCE vector", () => { const numValues = 5; const value = 10n; const columnMetadata = createColumnMetadata("testColumn", ScalarType.INT_64, false); const encodedData = encodeInt64DeltaRleColumn([[numValues, value]]); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, numValues); expect(result).toBeInstanceOf(Int64SequenceVector); const seqVec = result as Int64SequenceVector; expect(seqVec.getValue(0)).toBe(value); expect(seqVec.getValue(1)).toBe(value + value); expect(seqVec.getValue(2)).toBe(value + value * 2n); }); it("should decode INT_64 CONST vector", () => { const numValues = 5; const constValue = 42n; const columnMetadata = createColumnMetadata("testColumn", ScalarType.INT_64, false); const encodedData = encodeInt64RleColumn([[numValues, constValue]]); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, numValues); expect(result).toBeInstanceOf(Int64ConstVector); const constVec = result as Int64ConstVector; expect(constVec.getValue(0)).toBe(constValue); expect(constVec.getValue(4)).toBe(constValue); }); }); describe("decodePropertyColumn - UINT_64", () => { it("should decode UINT_64 column with NONE encoding (unsigned)", () => { const expectedValues = new BigUint64Array([2n, 4n, 6n, 100n]); const columnMetadata = createColumnMetadata("testColumn", ScalarType.UINT_64, false); const encodedData = encodeUint64Column(expectedValues); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, expectedValues.length); expect(result).toBeInstanceOf(Int64FlatVector); const resultVec = result as Int64FlatVector; for (let i = 0; i < expectedValues.length; i++) { expect(resultVec.getValue(i)).toBe(expectedValues[i]); } }); it("should decode UINT_64 max value in FLAT vector", () => { const expectedValues = new BigUint64Array([0xffffffffffffffffn, 1n]); const columnMetadata = createColumnMetadata("testColumn", ScalarType.UINT_64, false); const encodedData = encodeUint64Column(expectedValues); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, expectedValues.length); expect(result).toBeInstanceOf(Int64FlatVector); const resultVec = result as Int64FlatVector; expect(resultVec.getValue(0)).toBe(0xffffffffffffffffn); expect(resultVec.getValue(1)).toBe(1n); }); it("should decode UINT_64 CONST vector", () => { const expectedValue = 0xffffffffffffffffn; const columnMetadata = createColumnMetadata("testColumn", ScalarType.UINT_64, false); const encodedData = encodeUint64Column(new BigUint64Array([expectedValue])); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, 1); expect(result).toBeInstanceOf(Int64ConstVector); const constVec = result as Int64ConstVector; expect(constVec.getValue(0)).toBe(expectedValue); }); it("should decode nullable UINT_64 column with null values", () => { const expectedValues = [2n, null, 4n, null, 6n]; const columnMetadata = createColumnMetadata("testColumn", ScalarType.UINT_64, true); const encodedData = encodeUint64NullableColumn(expectedValues); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 2, expectedValues.length); expect(result).toBeInstanceOf(Int64FlatVector); const resultVec = result as Int64FlatVector; for (let i = 0; i < expectedValues.length; i++) { expect(resultVec.getValue(i)).toBe(expectedValues[i]); } }); }); describe("decodePropertyColumn - FLOAT", () => { it("should decode non-nullable FLOAT column", () => { const expectedValues = new Float32Array([1.5, 2.7, -3.14, 4.2]); const columnMetadata = createColumnMetadata("testColumn", ScalarType.FLOAT, false); const encodedData = encodeFloatColumn(expectedValues); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, expectedValues.length); expect(result).toBeInstanceOf(FloatFlatVector); const resultVec = result as FloatFlatVector; expect(resultVec.size).toBe(expectedValues.length); for (let i = 0; i < expectedValues.length; i++) { expect(resultVec.getValue(i)).toBeCloseTo(expectedValues[i], 5); } }); it("should decode nullable FLOAT column with null values", () => { const expectedValues = [1.5, null, 2.7, null, 3.14]; const columnMetadata = createColumnMetadata("testColumn", ScalarType.FLOAT, true); const encodedData = encodeFloatNullableColumn(expectedValues); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 2, expectedValues.length); expect(result).toBeInstanceOf(FloatFlatVector); const resultVec = result as FloatFlatVector; expect(resultVec.size).toBe(expectedValues.length); expect(resultVec.getValue(0)).toBeCloseTo(1.5, 5); expect(resultVec.getValue(1)).toBe(null); // null value expect(resultVec.getValue(2)).toBeCloseTo(2.7, 5); expect(resultVec.getValue(3)).toBe(null); // null value expect(resultVec.getValue(4)).toBeCloseTo(3.14, 5); }); it("should handle offset correctly after decoding FLOAT column", () => { const expectedValues = new Float32Array([1.0, 2.0, 3.0]); const columnMetadata = createColumnMetadata("testColumn", ScalarType.FLOAT, false); const encodedData = encodeFloatColumn(expectedValues); const offset = new IntWrapper(0); decodePropertyColumn(encodedData, offset, columnMetadata, 1, expectedValues.length); // Verify offset was advanced correctly expect(offset.get()).toBe(encodedData.length); }); }); describe("decodePropertyColumn - BOOLEAN", () => { it("should decode non-nullable BOOLEAN column with RLE", () => { const booleanValues = [true, false, true, true, false, false, false, true]; const columnMetadata = createColumnMetadata("testColumn", ScalarType.BOOLEAN, false); const encodedData = encodeBooleanColumn(booleanValues); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, booleanValues.length); expect(result).toBeInstanceOf(BooleanFlatVector); const boolVec = result as BooleanFlatVector; for (let i = 0; i < booleanValues.length; i++) { expect(boolVec.getValue(i)).toBe(booleanValues[i]); } }); it("should decode nullable BOOLEAN column with RLE and present stream", () => { const expectedValues = [true, null, false, null, true]; const columnMetadata = createColumnMetadata("testColumn", ScalarType.BOOLEAN, true); const encodedData = encodeBooleanNullableColumn(expectedValues); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 2, expectedValues.length); expect(result).toBeInstanceOf(BooleanFlatVector); const boolVec = result as BooleanFlatVector; expect(boolVec.getValue(0)).toBe(true); expect(boolVec.getValue(1)).toBe(null); expect(boolVec.getValue(2)).toBe(false); expect(boolVec.getValue(3)).toBe(null); expect(boolVec.getValue(4)).toBe(true); }); }); describe("decodePropertyColumn - DOUBLE", () => { it("should decode non-nullable DOUBLE column", () => { const expectedValues = new Float32Array([1.2345, 5.4321, 1.33742]); const columnMetadata = createColumnMetadata("testColumn", ScalarType.DOUBLE, false); const encodedData = encodeDoubleColumn(expectedValues); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 1, expectedValues.length); expect(result).toBeInstanceOf(DoubleFlatVector); const resultVec = result as DoubleFlatVector; expect(resultVec.size).toBe(expectedValues.length); for (let i = 0; i < expectedValues.length; i++) { expect(resultVec.getValue(i)).toBeCloseTo(expectedValues[i], 5); } }); it("should decode nullable DOUBLE column with null values", () => { const expectedValues = [1.5, null, 2.7, null, Math.PI]; const columnMetadata = createColumnMetadata("testColumn", ScalarType.DOUBLE, true); const encodedData = encodeDoubleNullableColumn(expectedValues); const offset = new IntWrapper(0); const result = decodePropertyColumn(encodedData, offset, columnMetadata, 2, expectedValues.length); expect(result).toBeInstanceOf(DoubleFlatVector); const resultVec = result as DoubleFlatVector; expect(resultVec.size).toBe(expectedValues.length); expect(resultVec.getValue(0)).toBeCloseTo(1.5, 5); expect(resultVec.getValue(1)).toBe(null); // null value expect(resultVec.getValue(2)).toBeCloseTo(2.7, 5); expect(resultVec.getValue(3)).toBe(null); // null value expect(resultVec.getValue(4)).toBeCloseTo(Math.PI, 5); }); it("should handle offset correctly after decoding DOUBLE column", () => { const expectedValues = new Float32Array([1.33742, 1.2345, 5.4321]); const columnMetadata = createColumnMetadata("testColumn", ScalarType.DOUBLE, false); const encodedData = encodeDoubleColumn(expectedValues); const offset = new IntWrapper(0); decodePropertyColumn(encodedData, offset, columnMetadata, 1, expectedValues.length); // Verify offset was advanced correctly expect(offset.get()).toBe(encodedData.length); }); }); describe("decodePropertyColumn - STRING", () => { describe("basic functionality", () => { it("should decode single field with shared dictionary", () => { const dictionaryStrings = ["apple", "banana", "peach", "date"]; const { lengthStream, dataStream } = encodeSharedDictionary(dictionaryStrings); const fieldStreams = encodeStructField([0, 1, 2, 3], [true, true, true, true]); const completeData = concatenateBuffers(lengthStream, dataStream, fieldStreams); const columnMetadata = createColumnMetadataForStruct("address:", [{ name: "street" }]); const offset = new IntWrapper(0); const result = decodePropertyColumn(completeData, offset, columnMetadata, 1, dictionaryStrings.length); expect(result).toHaveLength(1); expect((result as Vector[])[0]).toBeInstanceOf(StringDictionaryVector); expect((result as Vector[])[0].name).toBe("address:street"); for (let i = 0; i < dictionaryStrings.length; i++) { expect((result as Vector[])[0].getValue(i)).toBe(dictionaryStrings[i]); } }); it("should decode shared dictionary when numStreams matches encoder output (3 + N*2)", () => { const dictionaryStrings = ["apple", "banana", "peach", "date"]; const { lengthStream, dataStream } = encodeSharedDictionary(dictionaryStrings); const fieldStreams = encodeStructField([0, 1, 2, 3], [true, true, true, true]); const completeData = concatenateBuffers(lengthStream, dataStream, fieldStreams); const columnMetadata = createColumnMetadataForStruct("address:", [{ name: "street" }]); const offset = new IntWrapper(0); const result = decodePropertyColumn(completeData, offset, columnMetadata, 5, dictionaryStrings.length); expect(result).toHaveLength(1); expect((result as Vector[])[0]).toBeInstanceOf(StringDictionaryVector); expect((result as Vector[])[0].name).toBe("address:street"); for (let i = 0; i < dictionaryStrings.length; i++) { expect((result as Vector[])[0].getValue(i)).toBe(dictionaryStrings[i]); } }); }); }); describe("decodePropertyColumn - Edge Cases", () => { it("should filter columns with propertyColumnNames set", () => { const expectedValues = new Int32Array([1, 2, 3]); const columnMetadata = createColumnMetadata("includedColumn", ScalarType.INT_32, false); const encodedData = encodeInt32NoneColumn(expectedValues); const propertyColumnNames = new Set(["includedColumn"]); const offset = new IntWrapper(0); const result = decodePropertyColumn( encodedData, offset, columnMetadata, 1, expectedValues.length, propertyColumnNames, ); expect(result).toBeInstanceOf(Int32FlatVector); const resultVec = result as Int32FlatVector; for (let i = 0; i < expectedValues.length; i++) { expect(resultVec.getValue(i)).toBe(expectedValues[i]); } }); it("should skip column when not in propertyColumnNames filter", () => { const expectedValues = new Int32Array([1, 2, 3]); const columnMetadata = createColumnMetadata("excludedColumn", ScalarType.INT_32, false); const encodedData = encodeInt32NoneColumn(expectedValues); const propertyColumnNames = new Set(["someOtherColumn"]); const offset = new IntWrapper(0); const result = decodePropertyColumn( encodedData, offset, columnMetadata, 1, expectedValues.length, propertyColumnNames, ); // Should return null and advance the offset past the skipped data expect(result).toBe(null); expect(offset.get()).toBe(encodedData.length); }); it("should return null for empty columns (numStreams === 0)", () => { const columnMetadata = createColumnMetadata("emptyColumn", ScalarType.INT_32, false); const offset = new IntWrapper(0); const data = new Uint8Array(0); const result = decodePropertyColumn(data, offset, columnMetadata, 0, 0); expect(result).toBeNull(); }); it("should return null for complex type with numStreams === 0", () => { const columnMetadata = createColumnMetadataForStruct("structColumn", [{ name: "field1" }, { name: "field2" }]); const offset = new IntWrapper(0); const data = new Uint8Array(0); const result = decodePropertyColumn(data, offset, columnMetadata, 0, 5); expect(result).toBeNull(); }); it("should throw error for unsupported data type", () => { const columnMetadata = createColumnMetadata("unsupportedColumn", ScalarType.INT_8, false); const encodedData = encodeInt32NoneColumn(new Int32Array([1, 2, 3])); const offset = new IntWrapper(0); expect(() => { decodePropertyColumn(encodedData, offset, columnMetadata, 1, 3); }).toThrow(); }); }); ================================================ FILE: ts/src/decoding/propertyDecoder.ts ================================================ import type IntWrapper from "./intWrapper"; import { type Column, type ScalarColumn, ScalarType } from "../metadata/tileset/tilesetMetadata"; import type Vector from "../vector/vector"; import BitVector from "../vector/flat/bitVector"; import { decodeStreamMetadata, type RleEncodedStreamMetadata } from "../metadata/tile/streamMetadataDecoder"; import { VectorType } from "../vector/vectorType"; import { BooleanFlatVector } from "../vector/flat/booleanFlatVector"; import { DoubleFlatVector } from "../vector/flat/doubleFlatVector"; import { FloatFlatVector } from "../vector/flat/floatFlatVector"; import { Int64ConstVector } from "../vector/constant/int64ConstVector"; import { Int64FlatVector } from "../vector/flat/int64FlatVector"; import { Int32FlatVector } from "../vector/flat/int32FlatVector"; import { Int32ConstVector } from "../vector/constant/int32ConstVector"; import { decodeBooleanRle, decodeDoublesLE, decodeFloatsLE, skipColumn } from "./decodingUtils"; import { decodeSignedConstInt32Stream, decodeSignedConstInt64Stream, decodeSignedInt32Stream, decodeSignedInt64Stream, decodeUnsignedInt32Stream, decodeUnsignedConstInt32Stream, decodeUnsignedConstInt64Stream, decodeUnsignedInt64Stream, decodeSequenceInt32Stream, decodeSequenceInt64Stream, getVectorType, } from "./integerStreamDecoder"; import { Int32SequenceVector } from "../vector/sequence/int32SequenceVector"; import { Int64SequenceVector } from "../vector/sequence/int64SequenceVector"; import { decodeSharedDictionary, decodeString } from "./stringDecoder"; export function decodePropertyColumn( data: Uint8Array, offset: IntWrapper, columnMetadata: Column, numStreams: number, numFeatures: number, propertyColumnNames?: Set, ): Vector | Vector[] { if (columnMetadata.type === "scalarType") { if (propertyColumnNames && !propertyColumnNames.has(columnMetadata.name)) { skipColumn(numStreams, data, offset); return null; } return decodeScalarPropertyColumn( numStreams, data, offset, numFeatures, columnMetadata.scalarType, columnMetadata, ); } if (numStreams === 0) { return null; } return decodeSharedDictionary(data, offset, columnMetadata, propertyColumnNames); } function decodeScalarPropertyColumn( numStreams: number, data: Uint8Array, offset: IntWrapper, numFeatures: number, column: ScalarColumn, columnMetadata: Column, ) { let nullabilityBuffer: BitVector = null; if (numStreams === 0) { return null; } if (columnMetadata.nullable) { const presentStreamMetadata = decodeStreamMetadata(data, offset); const numValues = presentStreamMetadata.numValues; const streamDataStart = offset.get(); const presentVector = decodeBooleanRle(data, numValues, presentStreamMetadata.byteLength, offset); offset.set(streamDataStart + presentStreamMetadata.byteLength); nullabilityBuffer = new BitVector(presentVector, presentStreamMetadata.numValues); } const sizeOrNullabilityBuffer = nullabilityBuffer ?? numFeatures; const scalarType = column.physicalType; switch (scalarType) { case ScalarType.UINT_32: case ScalarType.INT_32: return decodeInt32Column(data, offset, columnMetadata, column, sizeOrNullabilityBuffer); case ScalarType.STRING: { // In embedded format: numStreams includes nullability stream if column is nullable const stringDataStreams = columnMetadata.nullable ? numStreams - 1 : numStreams; return decodeString(columnMetadata.name, data, offset, stringDataStreams, nullabilityBuffer); } case ScalarType.BOOLEAN: return decodeBooleanColumn(data, offset, columnMetadata, numFeatures, sizeOrNullabilityBuffer); case ScalarType.UINT_64: case ScalarType.INT_64: return decodeInt64Column(data, offset, columnMetadata, sizeOrNullabilityBuffer, column); case ScalarType.FLOAT: return decodeFloatColumn(data, offset, columnMetadata, sizeOrNullabilityBuffer); case ScalarType.DOUBLE: return decodeDoubleColumn(data, offset, columnMetadata, sizeOrNullabilityBuffer); default: throw new Error(`The specified data type for the field is currently not supported: ${column}`); } } function decodeBooleanColumn( data: Uint8Array, offset: IntWrapper, column: Column, _numFeatures: number, sizeOrNullabilityBuffer: number | BitVector, ): BooleanFlatVector { const dataStreamMetadata = decodeStreamMetadata(data, offset); const numValues = dataStreamMetadata.numValues; const streamDataStart = offset.get(); const nullabilityBuffer = isNullabilityBuffer(sizeOrNullabilityBuffer) ? sizeOrNullabilityBuffer : undefined; const dataStream = decodeBooleanRle(data, numValues, dataStreamMetadata.byteLength, offset, nullabilityBuffer); offset.set(streamDataStart + dataStreamMetadata.byteLength); const dataVector = new BitVector(dataStream, numValues); return new BooleanFlatVector(column.name, dataVector, sizeOrNullabilityBuffer); } function decodeFloatColumn( data: Uint8Array, offset: IntWrapper, column: Column, sizeOrNullabilityBuffer: number | BitVector, ): FloatFlatVector { const dataStreamMetadata = decodeStreamMetadata(data, offset); const nullabilityBuffer = isNullabilityBuffer(sizeOrNullabilityBuffer) ? sizeOrNullabilityBuffer : undefined; const dataStream = decodeFloatsLE(data, offset, dataStreamMetadata.numValues, nullabilityBuffer); return new FloatFlatVector(column.name, dataStream, sizeOrNullabilityBuffer); } function decodeDoubleColumn( data: Uint8Array, offset: IntWrapper, column: Column, sizeOrNullabilityBuffer: number | BitVector, ): DoubleFlatVector { const dataStreamMetadata = decodeStreamMetadata(data, offset); const nullabilityBuffer = isNullabilityBuffer(sizeOrNullabilityBuffer) ? sizeOrNullabilityBuffer : undefined; const dataStream = decodeDoublesLE(data, offset, dataStreamMetadata.numValues, nullabilityBuffer); return new DoubleFlatVector(column.name, dataStream, sizeOrNullabilityBuffer); } function decodeInt64Column( data: Uint8Array, offset: IntWrapper, column: Column, sizeOrNullabilityBuffer: number | BitVector, scalarColumn: ScalarColumn, ): Vector { const dataStreamMetadata = decodeStreamMetadata(data, offset); const vectorType = getVectorType(dataStreamMetadata, sizeOrNullabilityBuffer, data, offset, "int64"); const isSigned = scalarColumn.physicalType === ScalarType.INT_64; if (vectorType === VectorType.FLAT) { const nullabilityBuffer = isNullabilityBuffer(sizeOrNullabilityBuffer) ? sizeOrNullabilityBuffer : undefined; const dataStream = isSigned ? decodeSignedInt64Stream(data, offset, dataStreamMetadata, nullabilityBuffer) : decodeUnsignedInt64Stream(data, offset, dataStreamMetadata, nullabilityBuffer); return new Int64FlatVector(column.name, dataStream, sizeOrNullabilityBuffer); } if (vectorType === VectorType.SEQUENCE) { const id = decodeSequenceInt64Stream(data, offset, dataStreamMetadata); return new Int64SequenceVector( column.name, id[0], id[1], (dataStreamMetadata as RleEncodedStreamMetadata).numRleValues, ); } const constValue = isSigned ? decodeSignedConstInt64Stream(data, offset, dataStreamMetadata) : decodeUnsignedConstInt64Stream(data, offset, dataStreamMetadata); return new Int64ConstVector(column.name, constValue, sizeOrNullabilityBuffer, isSigned); } function decodeInt32Column( data: Uint8Array, offset: IntWrapper, column: Column, scalarColumn: ScalarColumn, sizeOrNullabilityBuffer: number | BitVector, ): Vector { const dataStreamMetadata = decodeStreamMetadata(data, offset); const vectorType = getVectorType(dataStreamMetadata, sizeOrNullabilityBuffer, data, offset); const isSigned = scalarColumn.physicalType === ScalarType.INT_32; if (vectorType === VectorType.FLAT) { const nullabilityBuffer = isNullabilityBuffer(sizeOrNullabilityBuffer) ? sizeOrNullabilityBuffer : undefined; const dataStream = isSigned ? decodeSignedInt32Stream(data, offset, dataStreamMetadata, undefined, nullabilityBuffer) : decodeUnsignedInt32Stream(data, offset, dataStreamMetadata, undefined, nullabilityBuffer); return new Int32FlatVector(column.name, dataStream, sizeOrNullabilityBuffer); } if (vectorType === VectorType.SEQUENCE) { const id = decodeSequenceInt32Stream(data, offset, dataStreamMetadata); return new Int32SequenceVector( column.name, id[0], id[1], (dataStreamMetadata as RleEncodedStreamMetadata).numRleValues, ); } const constValue = isSigned ? decodeSignedConstInt32Stream(data, offset, dataStreamMetadata) : decodeUnsignedConstInt32Stream(data, offset, dataStreamMetadata); return new Int32ConstVector(column.name, constValue, sizeOrNullabilityBuffer, isSigned); } function isNullabilityBuffer(sizeOrNullabilityBuffer: number | BitVector): sizeOrNullabilityBuffer is BitVector { return sizeOrNullabilityBuffer instanceof BitVector; } ================================================ FILE: ts/src/decoding/stringDecoder.spec.ts ================================================ import { describe, it, expect } from "vitest"; import IntWrapper from "./intWrapper"; import { decodeString, decodeSharedDictionary } from "./stringDecoder"; import { encodePlainStrings, encodeDictionaryStrings } from "../encoding/stringEncoder"; import { concatenateBuffers, createColumnMetadataForStruct, createStream, encodeFsstStrings, encodeSharedDictionary, encodeStructField, } from "./decodingTestUtils"; import { StringFlatVector } from "../vector/flat/stringFlatVector"; import { StringDictionaryVector } from "../vector/dictionary/stringDictionaryVector"; import { StringFsstDictionaryVector } from "../vector/fsst-dictionary/stringFsstDictionaryVector"; import { ScalarType } from "../metadata/tileset/tilesetMetadata"; import { PhysicalStreamType } from "../metadata/tile/physicalStreamType"; import { LengthType } from "../metadata/tile/lengthType"; describe("decodeString - Plain String Decoder", () => { it("should decode plain strings with simple ASCII values", () => { const expectedStrings = ["hello", "world", "test"]; const encodedStrings = encodePlainStrings(expectedStrings); const offset = new IntWrapper(0); const result = decodeString("testColumn", encodedStrings, offset, 2); expect(result).toBeInstanceOf(StringFlatVector); const resultVec = result as StringFlatVector; for (let i = 0; i < expectedStrings.length; i++) { expect(resultVec.getValue(i)).toBe(expectedStrings[i]); } }); it("should decode plain strings with varying lengths", () => { const expectedStrings = ["a", "abc", "hello world"]; const encodedStrings = encodePlainStrings(expectedStrings); const offset = new IntWrapper(0); const result = decodeString("testColumn", encodedStrings, offset, 2); const resultVec = result as StringFlatVector; for (let i = 0; i < expectedStrings.length; i++) { expect(resultVec.getValue(i)).toBe(expectedStrings[i]); } }); it("should decode plain strings with empty string", () => { const expectedStrings = [""]; const encodedStrings = encodePlainStrings(expectedStrings); const offset = new IntWrapper(0); const result = decodeString("testColumn", encodedStrings, offset, 2); expect(result).toBeInstanceOf(StringFlatVector); const resultVec = result as StringFlatVector; expect(resultVec.getValue(0)).toBe(expectedStrings[0]); }); it("should decode plain strings with empty strings", () => { const expectedStrings = ["", "encodedStrings", "", "more"]; const encodedStrings = encodePlainStrings(expectedStrings); const offset = new IntWrapper(0); const result = decodeString("testColumn", encodedStrings, offset, 2); expect(result).toBeInstanceOf(StringFlatVector); const resultVec = result as StringFlatVector; for (let i = 0; i < expectedStrings.length; i++) { expect(resultVec.getValue(i)).toBe(expectedStrings[i]); } }); it("should decode mixed null and empty strings", () => { const expectedStrings = [null, "", "encodedStrings", null, ""]; const encodedStrings = encodePlainStrings(expectedStrings); const offset = new IntWrapper(0); const result = decodeString("testColumn", encodedStrings, offset, 3); expect(result).not.toBeNull(); for (let i = 0; i < expectedStrings.length; i++) { expect(result.getValue(i)).toBe(expectedStrings[i]); } }); it("should decode mixed ASCII and UTF-8 strings", () => { const expectedStrings = ["hello", "Привет", "world", "日本"]; const encodedStrings = encodePlainStrings(expectedStrings); const offset = new IntWrapper(0); const result = decodeString("testColumn", encodedStrings, offset, 2); expect(result).toBeInstanceOf(StringFlatVector); const resultVec = result as StringFlatVector; for (let i = 0; i < expectedStrings.length; i++) { expect(resultVec.getValue(i)).toBe(expectedStrings[i]); } }); }); describe("decodeString - Dictionary String Decoder", () => { it("should decode dictionary-compressed strings with repeated values", () => { const expectedStrings = ["cat", "dog", "cat", "cat", "dog"]; const encodedStrings = encodeDictionaryStrings(expectedStrings); const offset = new IntWrapper(0); const result = decodeString("testColumn", encodedStrings, offset, 3); expect(result).toBeInstanceOf(StringDictionaryVector); const resultVec = result as StringDictionaryVector; for (let i = 0; i < expectedStrings.length; i++) { expect(resultVec.getValue(i)).toBe(expectedStrings[i]); } }); it("should decode dictionary with single repeated string", () => { const expectedStrings = ["same", "same", "same"]; const encodedStrings = encodeDictionaryStrings(expectedStrings); const offset = new IntWrapper(0); const result = decodeString("testColumn", encodedStrings, offset, 3); expect(result).toBeInstanceOf(StringDictionaryVector); for (let i = 0; i < expectedStrings.length; i++) { expect(result.getValue(i)).toBe(expectedStrings[i]); } }); it("should decode dictionary with UTF-8 strings", () => { const expectedStrings = ["café", "日本", "café", "日本"]; const encodedStrings = encodeDictionaryStrings(expectedStrings); const offset = new IntWrapper(0); const result = decodeString("testColumn", encodedStrings, offset, 3); expect(result).toBeInstanceOf(StringDictionaryVector); for (let i = 0; i < expectedStrings.length; i++) { expect(result.getValue(i)).toBe(expectedStrings[i]); } }); it("should decode dictionary with all unique strings", () => { const expectedStrings = ["unique1", "unique2", "unique3", "unique4"]; const encodedStrings = encodeDictionaryStrings(expectedStrings); const offset = new IntWrapper(0); const result = decodeString("testColumn", encodedStrings, offset, 3); expect(result).toBeInstanceOf(StringDictionaryVector); for (let i = 0; i < expectedStrings.length; i++) { expect(result.getValue(i)).toBe(expectedStrings[i]); } }); it("should decode nullable dictionary strings", () => { const expectedStrings = [null, "", "encodedStrings", "", null]; const encodedStrings = encodeDictionaryStrings(expectedStrings); const offset = new IntWrapper(0); const result = decodeString("testColumn", encodedStrings, offset, 4); expect(result).toBeInstanceOf(StringDictionaryVector); for (let i = 0; i < expectedStrings.length; i++) { expect(result.getValue(i)).toBe(expectedStrings[i]); } }); }); describe("decodeString - FSST Dictionary Decoder (Basic Coverage)", () => { it("should decode FSST-compressed strings with simple symbol table", () => { const encodedStrings = encodeFsstStrings(); const offset = new IntWrapper(0); const result = decodeString("testColumn", encodedStrings, offset, 6); expect(result).toBeInstanceOf(StringFsstDictionaryVector); const resultVec = result as StringFsstDictionaryVector; const expectedValues = ["cat", "dog", "cat"]; for (let i = 0; i < expectedValues.length; i++) { expect(resultVec.getValue(i)).toBe(expectedValues[i]); } }); }); describe("decodeString - Empty Column Edge Cases", () => { it("should handle empty column with numStreams = 0 (returns null)", () => { const fullStream = new Uint8Array([]); const offset = new IntWrapper(0); const result = decodeString("testColumn", fullStream, offset, 0); expect(result).toBeNull(); }); it("should handle column with all zero-length streams (returns null)", () => { const emptyStream = createStream(PhysicalStreamType.LENGTH, new Uint8Array([]), { logical: { lengthType: LengthType.VAR_BINARY }, }); const offset = new IntWrapper(0); const result = decodeString("testColumn", emptyStream, offset, 1); expect(result).toBeNull(); }); it("should handle single value plain string column", () => { const strings = ["single"]; const encodedStrings = encodePlainStrings(strings); const offset = new IntWrapper(0); const result = decodeString("testColumn", encodedStrings, offset, 2); expect(result).toBeInstanceOf(StringFlatVector); expect((result as StringFlatVector).getValue(0)).toBe("single"); }); it("should handle single null value in plain string column (returns null)", () => { const strings = [null]; const encodedStrings = encodePlainStrings(strings); const offset = new IntWrapper(0); const result = decodeString("testColumn", encodedStrings, offset, 3); expect((result as StringFlatVector).getValue(0)).toBeNull(); }); }); describe("decodeString - Integration Tests", () => { it("should correctly track offset through multiple streams", () => { const strings = ["hello", "world"]; const encodedStrings = encodePlainStrings(strings); const offset = new IntWrapper(0); const initialOffset = offset.get(); const result = decodeString("testColumn", encodedStrings, offset, 2); expect(result).toBeInstanceOf(StringFlatVector); expect(offset.get()).toBeGreaterThan(initialOffset); expect(offset.get()).toBe(encodedStrings.length); }); it("should correctly track offset through nullable streams", () => { const strings = ["test", null, "encodedStrings"]; const encodedStrings = encodePlainStrings(strings); const offset = new IntWrapper(0); const initialOffset = offset.get(); const result = decodeString("testColumn", encodedStrings, offset, 3); expect(result).not.toBeNull(); expect(offset.get()).toBeGreaterThan(initialOffset); expect(offset.get()).toBe(encodedStrings.length); }); it("should correctly track offset through FSST dictionary streams", () => { const encodedStrings = encodeFsstStrings(); const offset = new IntWrapper(0); const initialOffset = offset.get(); const result = decodeString("testColumn", encodedStrings, offset, 6); expect(result).toBeInstanceOf(StringFsstDictionaryVector); expect(offset.get()).toBeGreaterThan(initialOffset); expect(offset.get()).toBe(encodedStrings.length); }); it("should handle consecutive decoding operations with shared offset tracker", () => { const stream1 = encodePlainStrings(["first"]); const stream2 = encodePlainStrings(["second"]); const combinedStream = concatenateBuffers(stream1, stream2); const offset = new IntWrapper(0); const result1 = decodeString("column1", combinedStream, offset, 2); expect((result1 as StringFlatVector).getValue(0)).toBe("first"); const offsetAfterFirst = offset.get(); const result2 = decodeString("column2", combinedStream, offset, 2); expect((result2 as StringFlatVector).getValue(0)).toBe("second"); expect(offset.get()).toBeGreaterThan(offsetAfterFirst); expect(offset.get()).toBe(combinedStream.length); }); }); describe("decodeSharedDictionary", () => { describe("basic functionality", () => { it("should decode single field with shared dictionary", () => { const dictionaryStrings = ["apple", "banana", "peach", "date"]; const { lengthStream, dataStream } = encodeSharedDictionary(dictionaryStrings); const fieldStreams = encodeStructField([0, 1, 2, 3], [true, true, true, true]); const completeencodedStrings = concatenateBuffers(lengthStream, dataStream, fieldStreams); const columnMetaencodedStrings = createColumnMetadataForStruct("address:", [{ name: "street" }]); const result = decodeSharedDictionary(completeencodedStrings, new IntWrapper(0), columnMetaencodedStrings); expect(result).toHaveLength(1); expect(result[0]).toBeInstanceOf(StringDictionaryVector); expect(result[0].name).toBe("address:street"); for (let i = 0; i < dictionaryStrings.length; i++) { expect(result[0].getValue(i)).toBe(dictionaryStrings[i]); } }); it("should handle empty child field name (common prefix stripped)", () => { const dictionaryStrings = ["Berlin", "London", "Paris"]; const { lengthStream, dataStream } = encodeSharedDictionary(dictionaryStrings); const fieldStreams = encodeStructField([0, 1, 2], [true, true, true]); const completeencodedStrings = concatenateBuffers(lengthStream, dataStream, fieldStreams); const columnMetaencodedStrings = createColumnMetadataForStruct("name", [{ name: "" }]); const result = decodeSharedDictionary(completeencodedStrings, new IntWrapper(0), columnMetaencodedStrings); expect(result).toHaveLength(1); expect(result[0].name).toBe("name"); for (let i = 0; i < dictionaryStrings.length; i++) { expect(result[0].getValue(i)).toBe(dictionaryStrings[i]); } }); it("should handle mix of empty and delimited child names", () => { const dict = ["value1", "value2", "value3"]; const { lengthStream, dataStream } = encodeSharedDictionary(dict); const field1 = encodeStructField([0], [true]); const field2 = encodeStructField([1], [true]); const field3 = encodeStructField([2], [true]); const complete = concatenateBuffers(lengthStream, dataStream, field1, field2, field3); const metadata = createColumnMetadataForStruct("name", [{ name: "" }, { name: ":en" }, { name: ":de" }]); const result = decodeSharedDictionary(complete, new IntWrapper(0), metadata); expect(result).toHaveLength(3); expect(result[0].name).toBe("name"); expect(result[1].name).toBe("name:en"); expect(result[2].name).toBe("name:de"); }); }); describe("nullability", () => { it("should handle nullable fields with PRESENT stream", () => { const dictionaryStrings = ["red", "green", "blue"]; const { lengthStream, dataStream } = encodeSharedDictionary(dictionaryStrings); const fieldStreams = encodeStructField([0, 2], [true, false, true, false]); const completeencodedStrings = concatenateBuffers(lengthStream, dataStream, fieldStreams); const columnMetaencodedStrings = createColumnMetadataForStruct("colors:", [{ name: "primary" }]); const result = decodeSharedDictionary(completeencodedStrings, new IntWrapper(0), columnMetaencodedStrings); expect(result).toHaveLength(1); const expected = ["red", null, "blue", null]; for (let i = 0; i < expected.length; i++) { expect(result[0].getValue(i)).toBe(expected[i]); } }); it("should detect nullable fields when offsetCount < numFeatures", () => { const dictionaryStrings = ["alpha", "beta"]; const { lengthStream, dataStream } = encodeSharedDictionary(dictionaryStrings); // Simulating implicit nullability by mismatched counts const fieldStreams = encodeStructField([0, 1], [true, false, false, true]); const completeencodedStrings = concatenateBuffers(lengthStream, dataStream, fieldStreams); const columnMetaencodedStrings = createColumnMetadataForStruct("greek:", [{ name: "letter" }]); const result = decodeSharedDictionary(completeencodedStrings, new IntWrapper(0), columnMetaencodedStrings); expect(result).toHaveLength(1); const expected = ["alpha", null, null, "beta"]; for (let i = 0; i < expected.length; i++) { expect(result[0].getValue(i)).toBe(expected[i]); } }); }); describe("FSST encoding", () => { it("should decode FSST-compressed shared dictionary", () => { const dictionaryStrings = ["compressed1", "compressed2"]; const { lengthStream, dataStream, symbolLengthStream, symbolDataStream } = encodeSharedDictionary( dictionaryStrings, { useFsst: true }, ); const fieldStreams = encodeStructField([0, 1], [true, true]); const completeencodedStrings = concatenateBuffers( lengthStream, symbolLengthStream, symbolDataStream, dataStream, fieldStreams, ); const columnMetaencodedStrings = createColumnMetadataForStruct("encodedStrings:", [{ name: "value" }]); const result = decodeSharedDictionary(completeencodedStrings, new IntWrapper(0), columnMetaencodedStrings); expect(result).toHaveLength(1); expect(result[0]).toBeInstanceOf(StringFsstDictionaryVector); expect(result[0].name).toBe("encodedStrings:value"); }); }); describe("field filtering", () => { it("should filter fields by propertyColumnNames", () => { const dictionaryStrings = ["val1", "val2"]; const { lengthStream, dataStream } = encodeSharedDictionary(dictionaryStrings); const field1Streams = encodeStructField([0], [true]); const field2Streams = encodeStructField([1], [true]); const field3Streams = encodeStructField([0], [true]); const completeencodedStrings = concatenateBuffers( lengthStream, dataStream, field1Streams, field2Streams, field3Streams, ); const columnMetaencodedStrings = createColumnMetadataForStruct("multi:", [ { name: "field1" }, { name: "field2" }, { name: "field3" }, ]); const propertyColumnNames = new Set(["multi:field1", "multi:field3"]); const result = decodeSharedDictionary( completeencodedStrings, new IntWrapper(0), columnMetaencodedStrings, propertyColumnNames, ); expect(result).toHaveLength(2); expect(result[0].name).toBe("multi:field1"); expect(result[1].name).toBe("multi:field3"); }); it("should skip fields with numStreams=0", () => { const dictionaryStrings = ["present"]; const { lengthStream, dataStream } = encodeSharedDictionary(dictionaryStrings); const field1Streams = encodeStructField([0], [true], true); const field2Streams = encodeStructField([], [], false); // numStreams=0 const field3Streams = encodeStructField([0], [true], true); const completeencodedStrings = concatenateBuffers( lengthStream, dataStream, field1Streams, field2Streams, field3Streams, ); const columnMetaencodedStrings = createColumnMetadataForStruct("test:", [ { name: "field1" }, { name: "field2" }, { name: "field3" }, ]); const result = decodeSharedDictionary(completeencodedStrings, new IntWrapper(0), columnMetaencodedStrings); expect(result).toHaveLength(2); expect(result[0].name).toBe("test:field1"); expect(result[1].name).toBe("test:field3"); }); it("should handle mixed present and filtered fields", () => { const dictionaryStrings = ["encodedStrings"]; const { lengthStream, dataStream } = encodeSharedDictionary(dictionaryStrings); const field1Streams = encodeStructField([0], [true], true); const field2Streams = encodeStructField([], [], false); const field3Streams = encodeStructField([0], [true], true); const completeencodedStrings = concatenateBuffers( lengthStream, dataStream, field1Streams, field2Streams, field3Streams, ); const columnMetaencodedStrings = createColumnMetadataForStruct("mixed:", [ { name: "field1" }, { name: "field2" }, { name: "field3" }, ]); const propertyColumnNames = new Set(["mixed:field3"]); const result = decodeSharedDictionary( completeencodedStrings, new IntWrapper(0), columnMetaencodedStrings, propertyColumnNames, ); expect(result).toHaveLength(1); expect(result[0].name).toBe("mixed:field3"); }); }); describe("error handling", () => { it("should throw error for non-string field types", () => { const dictionaryStrings = ["value"]; const { lengthStream, dataStream } = encodeSharedDictionary(dictionaryStrings); const fieldStreams = encodeStructField([0], [true]); const completeEncodedStrings = concatenateBuffers(lengthStream, dataStream, fieldStreams); const columnMetaencodedStrings = createColumnMetadataForStruct("invalid", [ { name: "field1", type: ScalarType.INT_32 }, ]); expect(() => { decodeSharedDictionary(completeEncodedStrings, new IntWrapper(0), columnMetaencodedStrings); }).toThrow("Currently only scalar string fields are implemented for a struct."); }); it("should throw error for mismatched nullability and numStreams", () => { const dictionaryStrings = ["value"]; const { lengthStream, dataStream } = encodeSharedDictionary(dictionaryStrings); const fieldStreams = encodeStructField([0], [true]); const completeEncodedStrings = concatenateBuffers(lengthStream, dataStream, fieldStreams); const columnMetaencodedStrings = createColumnMetadataForStruct("invalidNullability", [ { name: "field1", type: ScalarType.STRING, nullable: false }, ]); expect(() => { decodeSharedDictionary(completeEncodedStrings, new IntWrapper(0), columnMetaencodedStrings); }).toThrow( "The number of streams for the child field field1 does not match its nullability. nullibilty: false, numStreams: 2", ); }); }); describe("offset tracking", () => { it("should correctly advance offset through all streams", () => { const dictionaryStrings = ["a", "b", "c"]; const { lengthStream, dataStream } = encodeSharedDictionary(dictionaryStrings); const field1Streams = encodeStructField([0, 1], [true, true]); const field2Streams = encodeStructField([1, 2], [true, true]); const completeencodedStrings = concatenateBuffers(lengthStream, dataStream, field1Streams, field2Streams); const columnMetaencodedStrings = createColumnMetadataForStruct("track", [ { name: "field1" }, { name: "field2" }, ]); const offset = new IntWrapper(0); const initialOffset = offset.get(); const result = decodeSharedDictionary(completeencodedStrings, offset, columnMetaencodedStrings); expect(result).toHaveLength(2); expect(offset.get()).toBeGreaterThan(initialOffset); expect(offset.get()).toBe(completeencodedStrings.length); }); }); }); ================================================ FILE: ts/src/decoding/stringDecoder.ts ================================================ import { decodeStreamMetadata } from "../metadata/tile/streamMetadataDecoder"; import { StringFlatVector } from "../vector/flat/stringFlatVector"; import { StringDictionaryVector } from "../vector/dictionary/stringDictionaryVector"; import type IntWrapper from "./intWrapper"; import BitVector from "../vector/flat/bitVector"; import type Vector from "../vector/vector"; import { PhysicalStreamType } from "../metadata/tile/physicalStreamType"; import { DictionaryType } from "../metadata/tile/dictionaryType"; import { LengthType } from "../metadata/tile/lengthType"; import { decodeUnsignedInt32Stream, decodeLengthStreamToOffsetBuffer } from "./integerStreamDecoder"; import { type Column, ScalarType } from "../metadata/tileset/tilesetMetadata"; import { decodeVarintInt32 } from "./integerDecodingUtils"; import { decodeBooleanRle, skipColumn } from "./decodingUtils"; import { StringFsstDictionaryVector } from "../vector/fsst-dictionary/stringFsstDictionaryVector"; export function decodeString( name: string, data: Uint8Array, offset: IntWrapper, numStreams: number, bitVector?: BitVector, ): Vector { let dictionaryLengthStream: Uint32Array = null; let offsetStream: Uint32Array = null; let dictionaryStream: Uint8Array = null; let symbolLengthStream: Uint32Array = null; let symbolTableStream: Uint8Array = null; let nullabilityBuffer: BitVector = bitVector ?? null; let plainLengthStream: Uint32Array = null; let plainDataStream: Uint8Array = null; for (let i = 0; i < numStreams; i++) { const streamMetadata = decodeStreamMetadata(data, offset); switch (streamMetadata.physicalStreamType) { case PhysicalStreamType.PRESENT: { const presentData = decodeBooleanRle(data, streamMetadata.numValues, streamMetadata.byteLength, offset); const presentStream = new BitVector(presentData, streamMetadata.numValues); nullabilityBuffer = bitVector ?? presentStream; break; } case PhysicalStreamType.OFFSET: { offsetStream = decodeUnsignedInt32Stream(data, offset, streamMetadata, undefined, nullabilityBuffer); break; } case PhysicalStreamType.LENGTH: { const lengthStream = decodeLengthStreamToOffsetBuffer(data, offset, streamMetadata); if (LengthType.DICTIONARY === streamMetadata.logicalStreamType.lengthType) { dictionaryLengthStream = lengthStream; } else if (LengthType.SYMBOL === streamMetadata.logicalStreamType.lengthType) { symbolLengthStream = lengthStream; } else { // Plain string encoding uses VAR_BINARY length type plainLengthStream = lengthStream; } break; } case PhysicalStreamType.DATA: { const dataStream = data.subarray(offset.get(), offset.get() + streamMetadata.byteLength); offset.add(streamMetadata.byteLength); const dictType = streamMetadata.logicalStreamType.dictionaryType; if (DictionaryType.FSST === dictType) { symbolTableStream = dataStream; } else if (DictionaryType.SINGLE === dictType || DictionaryType.SHARED === dictType) { dictionaryStream = dataStream; } else if (DictionaryType.NONE === dictType) { plainDataStream = dataStream; } break; } } } return ( decodeFsstDictionaryVector( name, symbolTableStream, offsetStream, dictionaryLengthStream, dictionaryStream, symbolLengthStream, nullabilityBuffer, ) ?? decodeDictionaryVector(name, dictionaryStream, offsetStream, dictionaryLengthStream, nullabilityBuffer) ?? decodePlainStringVector(name, plainLengthStream, plainDataStream, offsetStream, nullabilityBuffer) ); } function decodeFsstDictionaryVector( name: string, symbolTableStream: Uint8Array | null, offsetStream: Uint32Array | null, dictionaryLengthStream: Uint32Array | null, dictionaryStream: Uint8Array | null, symbolLengthStream: Uint32Array | null, nullabilityBuffer: BitVector | null, ): Vector | null { if (!symbolTableStream) { return null; } return new StringFsstDictionaryVector( name, offsetStream, dictionaryLengthStream, dictionaryStream, symbolLengthStream, symbolTableStream, nullabilityBuffer, ); } function decodeDictionaryVector( name: string, dictionaryStream: Uint8Array | null, offsetStream: Uint32Array | null, dictionaryLengthStream: Uint32Array | null, nullabilityBuffer: BitVector | null, ): Vector | null { if (!dictionaryStream) { return null; } return nullabilityBuffer ? new StringDictionaryVector(name, offsetStream, dictionaryLengthStream, dictionaryStream, nullabilityBuffer) : new StringDictionaryVector(name, offsetStream, dictionaryLengthStream, dictionaryStream); } function decodePlainStringVector( name: string, plainLengthStream: Uint32Array | null, plainDataStream: Uint8Array | null, offsetStream: Uint32Array | null, nullabilityBuffer: BitVector | null, ): Vector | null { if (!plainLengthStream || !plainDataStream) { return null; } if (offsetStream) { return nullabilityBuffer ? new StringDictionaryVector(name, offsetStream, plainLengthStream, plainDataStream, nullabilityBuffer) : new StringDictionaryVector(name, offsetStream, plainLengthStream, plainDataStream); } if (nullabilityBuffer && nullabilityBuffer.size() !== plainLengthStream.length - 1) { const sparseOffsetStream = new Uint32Array(nullabilityBuffer.size()); let valueIndex = 0; for (let i = 0; i < nullabilityBuffer.size(); i++) { if (nullabilityBuffer.get(i)) { sparseOffsetStream[i] = valueIndex++; } else { sparseOffsetStream[i] = 0; } } return new StringDictionaryVector( name, sparseOffsetStream, plainLengthStream, plainDataStream, nullabilityBuffer, ); } return nullabilityBuffer ? new StringFlatVector(name, plainLengthStream, plainDataStream, nullabilityBuffer) : new StringFlatVector(name, plainLengthStream, plainDataStream); } export function decodeSharedDictionary( data: Uint8Array, offset: IntWrapper, column: Column, propertyColumnNames?: Set, ): Vector[] { let dictionaryOffsetBuffer: Uint32Array = null; let dictionaryBuffer: Uint8Array = null; let symbolOffsetBuffer: Uint32Array = null; let symbolTableBuffer: Uint8Array = null; let dictionaryStreamDecoded = false; while (!dictionaryStreamDecoded) { const streamMetadata = decodeStreamMetadata(data, offset); switch (streamMetadata.physicalStreamType) { case PhysicalStreamType.LENGTH: if (LengthType.DICTIONARY === streamMetadata.logicalStreamType.lengthType) { dictionaryOffsetBuffer = decodeLengthStreamToOffsetBuffer(data, offset, streamMetadata); } else { symbolOffsetBuffer = decodeLengthStreamToOffsetBuffer(data, offset, streamMetadata); } break; case PhysicalStreamType.DATA: if ( DictionaryType.SINGLE === streamMetadata.logicalStreamType.dictionaryType || DictionaryType.SHARED === streamMetadata.logicalStreamType.dictionaryType ) { dictionaryBuffer = data.subarray(offset.get(), offset.get() + streamMetadata.byteLength); dictionaryStreamDecoded = true; } else { symbolTableBuffer = data.subarray(offset.get(), offset.get() + streamMetadata.byteLength); } offset.add(streamMetadata.byteLength); break; } } const childFields = column.complexType.children; const stringDictionaryVectors = []; let i = 0; for (const childField of childFields) { const numStreams = decodeVarintInt32(data, offset, 1)[0]; if (numStreams === 0) { /* Column is not present in the tile */ continue; } const columnName = childField.name ? `${column.name}${childField.name}` : column.name; if (propertyColumnNames) { if (!propertyColumnNames.has(columnName)) { //TODO: add size of sub column to Mlt for faster skipping skipColumn(numStreams, data, offset); continue; } } if (childField.type !== "scalarField" || childField.scalarField.physicalType !== ScalarType.STRING) { throw new Error("Currently only scalar string fields are implemented for a struct."); } if ((numStreams > 1 && !childField.nullable) || (numStreams === 1 && childField.nullable)) { throw new Error( `The number of streams for the child field ${childField.name} does not match its nullability. nullibilty: ${childField.nullable}, numStreams: ${numStreams}`, ); } let presentStreamBitVector: BitVector | undefined; if (childField.nullable) { const presentStreamMetadata = decodeStreamMetadata(data, offset); const presentStream = decodeBooleanRle( data, presentStreamMetadata.numValues, presentStreamMetadata.byteLength, offset, ); presentStreamBitVector = new BitVector(presentStream, presentStreamMetadata.numValues); } const offsetStreamMetadata = decodeStreamMetadata(data, offset); const offsetStream = decodeUnsignedInt32Stream( data, offset, offsetStreamMetadata, undefined, presentStreamBitVector, ); stringDictionaryVectors[i++] = symbolTableBuffer ? new StringFsstDictionaryVector( columnName, offsetStream, dictionaryOffsetBuffer, dictionaryBuffer, symbolOffsetBuffer, symbolTableBuffer, presentStreamBitVector, ) : new StringDictionaryVector( columnName, offsetStream, dictionaryOffsetBuffer, dictionaryBuffer, presentStreamBitVector, ); } return stringDictionaryVectors; } ================================================ FILE: ts/src/decoding/unpackNullableUtils.spec.ts ================================================ import { describe, it, expect } from "vitest"; import { unpackNullable, unpackNullableBoolean } from "./unpackNullableUtils"; import BitVector from "../vector/flat/bitVector"; import { packNullable, packNullableBoolean } from "../encoding/packNullableUtils"; describe("unpackNullableUtils", () => { describe("unpackNullable", () => { it("should return the original array when presentBits is null", () => { const dataStream = new Int32Array([1, 2, 3]); const packed = packNullable(dataStream, null); const result = unpackNullable(packed, null, 0); expect(result).toBe(dataStream); expect(result).toEqual(new Int32Array([1, 2, 3])); }); it("should return the original array when presentBits is undefined", () => { const dataStream = new Float32Array([1.5, 2.5, 3.5]); const result = unpackNullable(dataStream, undefined, 0); expect(result).toBe(dataStream); expect(result).toEqual(new Float32Array([1.5, 2.5, 3.5])); }); it("should return the original BigInt64Array when presentBits is null", () => { const dataStream = new BigInt64Array([10n, 20n, 30n]); const result = unpackNullable(dataStream, null, 0n); expect(result).toBe(dataStream); expect(result).toEqual(new BigInt64Array([10n, 20n, 30n])); }); it("should return the original array when presentBits is fully set", () => { const dataStream = new BigInt64Array([10n, 20n, 30n]); const presentBits = new BitVector(new Uint8Array([0b111]), 3); const packed = packNullable(dataStream, presentBits); const result = unpackNullable(packed, presentBits, 0n); expect(result).toEqual(new BigInt64Array([10n, 20n, 30n])); }); it("should return partial array when presentBits is partially set", () => { const dataStream = new Int32Array([0, 20, 30]); // first number is "null" const presentBits = new BitVector(new Uint8Array([0b110]), 3); const packed = packNullable(dataStream, presentBits); const result = unpackNullable(packed, presentBits, 0); expect(result).toEqual(new Int32Array([0, 20, 30])); }); }); describe("unpackNullableBoolean", () => { it("should return the original array when presentBits is null", () => { const dataStream = new Uint8Array([0b11010101]); const packed = packNullableBoolean(dataStream, 8, null); const result = unpackNullableBoolean(packed, 8, null); expect(result).toBe(dataStream); expect(result).toEqual(new Uint8Array([0b11010101])); }); it("should return the original array when presentBits is undefined", () => { const dataStream = new Uint8Array([0b00001111]); const result = unpackNullableBoolean(dataStream, 8, undefined); expect(result).toBe(dataStream); expect(result).toEqual(new Uint8Array([0b00001111])); }); it("should return the original array when presentBits is fully set", () => { const dataStream = new Uint8Array([0b11010101]); const presentBits = new BitVector(new Uint8Array([0b11111111]), 8); const packed = packNullableBoolean(dataStream, 8, presentBits); const result = unpackNullableBoolean(packed, 8, presentBits); expect(result).toEqual(new Uint8Array([0b11010101])); }); it("should return partial array when presentBits is partially set", () => { const dataStream = new Uint8Array([0b11111111]); const presentBits = new BitVector(new Uint8Array([0b11110000]), 8); const packed = packNullableBoolean(dataStream, 8, presentBits); const result = unpackNullableBoolean(packed, 8, presentBits); expect(result).toEqual(new Uint8Array([0b11110000])); }); }); }); ================================================ FILE: ts/src/decoding/unpackNullableUtils.ts ================================================ import BitVector from "../vector/flat/bitVector.js"; /** * Type constraint for TypedArray types that can be unpacked */ export type TypedArrayConstructor = | Int32ArrayConstructor | Uint32ArrayConstructor | BigInt64ArrayConstructor | BigUint64ArrayConstructor | Float32ArrayConstructor | Float64ArrayConstructor; export type TypedArrayInstance = | Int32Array | Uint32Array | BigInt64Array | BigUint64Array | Float32Array | Float64Array; /** * Generic unpacking function. * Reconstructs the full array by inserting default values at null positions. * * @param dataStream The compact data stream containing only non-null values * @param presentBits BitVector indicating which positions have values (null if non-nullable) * @param defaultValue The default value to insert at null positions (0, 0n, etc.) * @returns Full array with default values at null positions */ export function unpackNullable( dataStream: T, presentBits: BitVector | null, defaultValue: number | bigint, ): T { // Non-nullable case: return data stream as-is if (!presentBits) { return dataStream; } const size = presentBits.size(); // Create new array of same type with full size const constructor = dataStream.constructor as TypedArrayConstructor; const result = new constructor(size) as T; let counter = 0; for (let i = 0; i < size; i++) { // If position has a value, take from data stream; otherwise use default result[i] = presentBits.get(i) ? dataStream[counter++] : (defaultValue as any); } return result; } /** * Special case for boolean columns because BitVector is not directly compatible with TypedArray. * * @param dataStream The compact BitVector data containing only non-null boolean values * @param dataStreamSize The number of actual values in dataStream * @param presentBits BitVector indicating which positions have values (null if non-nullable) * @returns Uint8Array buffer for BitVector with false at null positions */ export function unpackNullableBoolean( dataStream: Uint8Array, dataStreamSize: number, presentBits: BitVector | null, ): Uint8Array { // Non-nullable case if (!presentBits) { return dataStream; } const numFeatures = presentBits.size(); const bitVector = new BitVector(dataStream, dataStreamSize); const result = new BitVector(new Uint8Array(Math.ceil(numFeatures / 8)), numFeatures); let counter = 0; for (let i = 0; i < numFeatures; i++) { // If position has a value, take from data stream; otherwise use false const value = presentBits.get(i) ? bitVector.get(counter++) : false; result.set(i, value); } return result.getBuffer(); } ================================================ FILE: ts/src/encoding/bigEndianEncode.ts ================================================ import { bswap32 } from "../decoding/fastPforShared"; /** * Serializes an `Int32Array` to a big-endian byte stream. * * @param values - Int32 words to serialize. * @returns Big-endian byte stream (`values.length * 4` bytes). */ export function encodeBigEndianInt32s(values: Uint32Array): Uint8Array { const bytes = new Uint8Array(values.length * 4); const u32 = new Uint32Array(bytes.buffer, bytes.byteOffset, values.length); for (let i = 0; i < values.length; i++) { u32[i] = bswap32(values[i]); } return bytes; } ================================================ FILE: ts/src/encoding/constGeometryVectorEncoder.ts ================================================ import { ConstGeometryVector } from "../vector/geometry/constGeometryVector"; import { GEOMETRY_TYPE } from "../vector/geometry/geometryType"; import { VertexBufferType } from "../vector/geometry/vertexBufferType"; import { encodeZOrderCurve } from "./zOrderCurveEncoder"; import type { GeometryVector, MortonSettings } from "../vector/geometry/geometryVector"; export const DEFAULT_MORTON_SETTINGS: MortonSettings = { numBits: 16, coordinateShift: 0 } as MortonSettings; export function encode(x: number, y: number): number { return encodeZOrderCurve(x, y, DEFAULT_MORTON_SETTINGS.numBits, DEFAULT_MORTON_SETTINGS.coordinateShift); } export function encodePointGeometryVector(x: number, y: number): GeometryVector { return new ConstGeometryVector( 1, GEOMETRY_TYPE.POINT, VertexBufferType.VEC_2, { geometryOffsets: new Uint32Array([0]), partOffsets: new Uint32Array([0]), ringOffsets: new Uint32Array([0]), }, undefined, new Int32Array([x, y]), ); } export function encodePointGeometryVectorWithOffset(x: number, y: number): GeometryVector { return new ConstGeometryVector( 1, GEOMETRY_TYPE.POINT, VertexBufferType.VEC_2, { geometryOffsets: new Uint32Array([0]), partOffsets: new Uint32Array([0]), ringOffsets: new Uint32Array([0]), }, new Uint32Array([1]), new Int32Array([99, 99, x, y]), ); } export function encodePointGeometryVectorWithMortonEncoding(x: number, y: number): GeometryVector { const mortonEncoded = encode(x, y); return new ConstGeometryVector( 1, GEOMETRY_TYPE.POINT, VertexBufferType.MORTON, { geometryOffsets: new Uint32Array([0]), partOffsets: new Uint32Array([0]), ringOffsets: new Uint32Array([0]), }, new Uint32Array([0]), new Int32Array([mortonEncoded]), DEFAULT_MORTON_SETTINGS, ); } export function encodePointsGeometryVector(points: number[]): GeometryVector { return new ConstGeometryVector( points.length / 2, GEOMETRY_TYPE.POINT, VertexBufferType.VEC_2, { geometryOffsets: new Uint32Array([0]), partOffsets: new Uint32Array([0]), ringOffsets: new Uint32Array([0]), }, undefined, new Int32Array(points), ); } export function encodeMultiPointGeometryVector(points: number[][]): GeometryVector { const vertexBuffer = new Int32Array(points.flatMap((point) => [point[0], point[1]])); return new ConstGeometryVector( 1, GEOMETRY_TYPE.MULTIPOINT, VertexBufferType.VEC_2, { geometryOffsets: new Uint32Array([0, points.length]), partOffsets: undefined, ringOffsets: undefined, }, undefined, vertexBuffer, ); } export function encodeLineStringGeometryVector(lines: [number, number][]): GeometryVector { const vertexBuffer = new Int32Array(lines.flatMap((line) => [line[0], line[1]])); return new ConstGeometryVector( 1, GEOMETRY_TYPE.LINESTRING, VertexBufferType.VEC_2, { geometryOffsets: undefined, partOffsets: new Uint32Array([0, vertexBuffer.length / 2]), ringOffsets: undefined, }, undefined, vertexBuffer, ); } export function encodeLineStringGeometryVectorWithMortonEncoding(line: [number, number][]): GeometryVector { const numVertices = line.length; const vertexBuffer = new Int32Array(numVertices); const offsetBuffer = new Uint32Array(numVertices); for (let i = 0; i < numVertices; i++) { vertexBuffer[i] = encode(line[i][0], line[i][1]); offsetBuffer[i] = i; } return new ConstGeometryVector( 1, GEOMETRY_TYPE.LINESTRING, VertexBufferType.MORTON, { geometryOffsets: undefined, partOffsets: new Uint32Array([0, numVertices]), ringOffsets: undefined, }, offsetBuffer, vertexBuffer, DEFAULT_MORTON_SETTINGS, ); } export function encodePolygonGeometryVector(polygon: [number, number][][]): GeometryVector { const vertexBuffer = new Int32Array(polygon.flatMap((ring) => ring.flatMap((point) => [point[0], point[1]]))); const ringOffsets = new Uint32Array(polygon.length + 1); ringOffsets[0] = 0; let ringIndex = 1; for (const ring of polygon) { ringOffsets[ringIndex] = ringOffsets[ringIndex - 1] + ring.length; ringIndex++; } return new ConstGeometryVector( 1, GEOMETRY_TYPE.POLYGON, VertexBufferType.VEC_2, { geometryOffsets: undefined, partOffsets: new Uint32Array([0, polygon.length]), ringOffsets, }, undefined, vertexBuffer, ); } export function encodePolygonGeometryVectorWithOffsets(polygon: [number, number][][]): GeometryVector { const vertexBuffer = new Int32Array(polygon.flatMap((ring) => ring.flatMap((point) => [point[0], point[1]]))); const ringOffsets = new Uint32Array(polygon.length + 1); ringOffsets[0] = 0; let ringIndex = 1; for (const ring of polygon) { ringOffsets[ringIndex] = ringOffsets[ringIndex - 1] + ring.length; ringIndex++; } const offsetBuffer = new Uint32Array(vertexBuffer.length / 2); for (let i = 0; i < offsetBuffer.length; i++) { offsetBuffer[i] = i; } return new ConstGeometryVector( 1, GEOMETRY_TYPE.POLYGON, VertexBufferType.VEC_2, { geometryOffsets: undefined, partOffsets: new Uint32Array([0, polygon.length]), ringOffsets, }, offsetBuffer, vertexBuffer, ); } export function encodePolygonGeometryVectorWithMortonOffsets(polygon: [number, number][][]): GeometryVector { const vertexBuffer = new Int32Array(polygon.flatMap((ring) => ring.flatMap((point) => encode(point[0], point[1])))); const ringOffsets = new Uint32Array(polygon.length + 1); ringOffsets[0] = 0; let ringIndex = 1; for (const ring of polygon) { ringOffsets[ringIndex] = ringOffsets[ringIndex - 1] + ring.length; ringIndex++; } const offsetBuffer = new Uint32Array(vertexBuffer.length); for (let i = 0; i < offsetBuffer.length; i++) { offsetBuffer[i] = i; } return new ConstGeometryVector( 1, GEOMETRY_TYPE.POLYGON, VertexBufferType.MORTON, { geometryOffsets: undefined, partOffsets: new Uint32Array([0, polygon.length]), ringOffsets, }, offsetBuffer, vertexBuffer, DEFAULT_MORTON_SETTINGS, ); } export function encodeMultiLineStringGeometryVector(lines: [number, number][][]): GeometryVector { const vertexBuffer = new Int32Array(lines.flatMap((line) => line.flatMap((point) => [point[0], point[1]]))); const partOffsets = new Uint32Array(lines.length + 1); partOffsets[0] = 0; let partIndex = 1; for (const line of lines) { partOffsets[partIndex] = partOffsets[partIndex - 1] + line.length; partIndex++; } return new ConstGeometryVector( 1, GEOMETRY_TYPE.MULTILINESTRING, VertexBufferType.VEC_2, { geometryOffsets: new Uint32Array([0, lines.length]), partOffsets, ringOffsets: undefined, }, undefined, vertexBuffer, ); } export function encodeMultiLineStringGeometryVectorWithOffsets(lines: [number, number][][]): GeometryVector { const vertexBuffer = new Int32Array(lines.flatMap((line) => line.flatMap((point) => [point[0], point[1]]))); const partOffsets = new Uint32Array(lines.length + 1); partOffsets[0] = 0; let partIndex = 1; for (const line of lines) { partOffsets[partIndex] = partOffsets[partIndex - 1] + line.length; partIndex++; } const offsetBuffer = new Uint32Array(vertexBuffer.length / 2); for (let i = 0; i < offsetBuffer.length; i++) { offsetBuffer[i] = i; } return new ConstGeometryVector( 1, GEOMETRY_TYPE.MULTILINESTRING, VertexBufferType.VEC_2, { geometryOffsets: new Uint32Array([0, lines.length]), partOffsets, ringOffsets: undefined, }, offsetBuffer, vertexBuffer, ); } export function encodeMultiLineStringGeometryVectorWithMortonOffsets(lines: [number, number][][]): GeometryVector { const vertexBuffer = new Int32Array(lines.flatMap((line) => line.flatMap((point) => encode(point[0], point[1])))); const partOffsets = new Uint32Array(lines.length + 1); partOffsets[0] = 0; let partIndex = 1; for (const line of lines) { partOffsets[partIndex] = partOffsets[partIndex - 1] + line.length; partIndex++; } const offsetBuffer = new Uint32Array(vertexBuffer.length); for (let i = 0; i < offsetBuffer.length; i++) { offsetBuffer[i] = i; } return new ConstGeometryVector( 1, GEOMETRY_TYPE.MULTILINESTRING, VertexBufferType.MORTON, { geometryOffsets: new Uint32Array([0, lines.length]), partOffsets, ringOffsets: undefined, }, offsetBuffer, vertexBuffer, DEFAULT_MORTON_SETTINGS, ); } export function encodeMultiPolygonGeometryVector(polygons: [number, number][][][]): GeometryVector { const vertexBuffer = new Int32Array( polygons.flatMap((polygon) => polygon.flatMap((ring) => ring.flatMap((point) => [point[0], point[1]]))), ); const ringOffsets = new Uint32Array(polygons.reduce((sum, polygon) => sum + polygon.length, 0) + 1); const partOffsets = new Uint32Array(polygons.length + 1); ringOffsets[0] = 0; partOffsets[0] = 0; let ringIndex = 1; let partIndex = 1; for (const polygon of polygons) { for (const ring of polygon) { ringOffsets[ringIndex] = ringOffsets[ringIndex - 1] + ring.length; ringIndex++; } partOffsets[partIndex] = partOffsets[partIndex - 1] + polygon.length; partIndex++; } return new ConstGeometryVector( 1, GEOMETRY_TYPE.MULTIPOLYGON, VertexBufferType.VEC_2, { geometryOffsets: new Uint32Array([0, polygons.length]), partOffsets, ringOffsets, }, undefined, vertexBuffer, ); } export function encodeMultiPolygonGeometryVectorWithOffsets(polygons: [number, number][][][]): GeometryVector { const vertexBuffer = new Int32Array( polygons.flatMap((polygon) => polygon.flatMap((ring) => ring.flatMap((point) => [point[0], point[1]]))), ); const ringOffsets = new Uint32Array(polygons.reduce((sum, polygon) => sum + polygon.length, 0) + 1); const partOffsets = new Uint32Array(polygons.length + 1); ringOffsets[0] = 0; partOffsets[0] = 0; let ringIndex = 1; let partIndex = 1; for (const polygon of polygons) { for (const ring of polygon) { ringOffsets[ringIndex] = ringOffsets[ringIndex - 1] + ring.length; ringIndex++; } partOffsets[partIndex] = partOffsets[partIndex - 1] + polygon.length; partIndex++; } const offsetBuffer = new Uint32Array(vertexBuffer.length / 2); for (let i = 0; i < offsetBuffer.length; i++) { offsetBuffer[i] = i; } return new ConstGeometryVector( 1, GEOMETRY_TYPE.MULTIPOLYGON, VertexBufferType.VEC_2, { geometryOffsets: new Uint32Array([0, polygons.length]), partOffsets, ringOffsets, }, offsetBuffer, vertexBuffer, ); } export function encodeMultiPolygonGeometryVectorWithMortonOffsets(polygons: [number, number][][][]): GeometryVector { const vertexBuffer = new Int32Array( polygons.flatMap((polygon) => polygon.flatMap((ring) => ring.flatMap((point) => encode(point[0], point[1])))), ); const ringOffsets = new Uint32Array(polygons.reduce((sum, polygon) => sum + polygon.length, 0) + 1); const partOffsets = new Uint32Array(polygons.length + 1); ringOffsets[0] = 0; partOffsets[0] = 0; let ringIndex = 1; let partIndex = 1; for (const polygon of polygons) { for (const ring of polygon) { ringOffsets[ringIndex] = ringOffsets[ringIndex - 1] + ring.length; ringIndex++; } partOffsets[partIndex] = partOffsets[partIndex - 1] + polygon.length; partIndex++; } const offsetBuffer = new Uint32Array(vertexBuffer.length); for (let i = 0; i < offsetBuffer.length; i++) { offsetBuffer[i] = i; } return new ConstGeometryVector( 1, GEOMETRY_TYPE.MULTIPOLYGON, VertexBufferType.MORTON, { geometryOffsets: new Uint32Array([0, polygons.length]), partOffsets, ringOffsets, }, offsetBuffer, vertexBuffer, DEFAULT_MORTON_SETTINGS, ); } ================================================ FILE: ts/src/encoding/embeddedTilesetMetadataEncoder.ts ================================================ import IntWrapper from "../decoding/intWrapper"; import { encodeVarintInt32Value } from "./integerEncodingUtils"; import { concatenateBuffers } from "../decoding/decodingTestUtils"; /** * Encodes a single typeCode as a varint. */ export function encodeTypeCode(typeCode: number): Uint8Array { const buffer = new Uint8Array(5); const offset = new IntWrapper(0); encodeVarintInt32Value(typeCode, buffer, offset); return buffer.slice(0, offset.get()); } /** * Encodes a field name as a length-prefixed UTF-8 string. */ export function encodeFieldName(name: string): Uint8Array { const textEncoder = new TextEncoder(); const nameBytes = textEncoder.encode(name); const lengthBuf = new Uint8Array(5); const offset = new IntWrapper(0); encodeVarintInt32Value(nameBytes.length, lengthBuf, offset); const lengthSlice = lengthBuf.slice(0, offset.get()); return concatenateBuffers(lengthSlice, nameBytes); } /** * Encodes a child count as a varint. */ export function encodeChildCount(count: number): Uint8Array { const buffer = new Uint8Array(5); const offset = new IntWrapper(0); encodeVarintInt32Value(count, buffer, offset); return buffer.slice(0, offset.get()); } /** * Computes typeCode for a scalar field. */ export function scalarTypeCode(scalarType: number, nullable: boolean): number { return 10 + scalarType * 2 + (nullable ? 1 : 0); } ================================================ FILE: ts/src/encoding/encodingUtils.ts ================================================ export function encodeFloatsLE(values: Float32Array): Uint8Array { const buffer = new Uint8Array(values.length * 4); const view = new DataView(buffer.buffer); for (let i = 0; i < values.length; i++) { view.setFloat32(i * 4, values[i], true); } return buffer; } export function encodeDoubleLE(values: Float64Array): Uint8Array { const buffer = new Uint8Array(values.length * Float64Array.BYTES_PER_ELEMENT); const view = new DataView(buffer.buffer); for (let i = 0; i < values.length; i++) { view.setFloat64(i * Float64Array.BYTES_PER_ELEMENT, values[i], true); } return buffer; } export function encodeBooleanRle(values: boolean[]): Uint8Array { // Pack booleans into bytes (8 booleans per byte) const numBytes = Math.ceil(values.length / 8); const packed = new Uint8Array(numBytes); for (let i = 0; i < values.length; i++) { if (values[i]) { const byteIndex = Math.floor(i / 8); const bitIndex = i % 8; packed[byteIndex] |= 1 << bitIndex; } } const result = new Uint8Array(1 + numBytes); result[0] = 256 - numBytes; result.set(packed, 1); return result; } export function encodeByteRle(values: Uint8Array): Uint8Array { const result: number[] = []; let i = 0; while (i < values.length) { const currentByte = values[i]; let runLength = 1; while (i + runLength < values.length && values[i + runLength] === currentByte && runLength < 131) { runLength++; } if (runLength >= 3) { const header = runLength - 3; result.push(Math.min(header, 0x7f)); result.push(currentByte); i += runLength; } else { const literalStart = i; while (i < values.length) { let nextRunLength = 1; if (i + 1 < values.length) { while ( i + nextRunLength < values.length && values[i + nextRunLength] === values[i] && nextRunLength < 3 ) { nextRunLength++; } } if (nextRunLength >= 3) { break; } i++; if (i - literalStart >= 128) { break; } } const numLiterals = i - literalStart; const header = 256 - numLiterals; result.push(header); for (let j = literalStart; j < i; j++) { result.push(values[j]); } } } return new Uint8Array(result); } export function encodeStrings(strings: string[]): Uint8Array { const encoder = new TextEncoder(); const encoded = strings.map((s) => encoder.encode(s)); const totalLength = encoded.reduce((sum, arr) => sum + arr.length, 0); const result = new Uint8Array(totalLength); let offset = 0; for (const arr of encoded) { result.set(arr, offset); offset += arr.length; } return result; } export function createStringLengths(strings: string[]): Uint32Array { const lengths = new Uint32Array(strings.length); const encoder = new TextEncoder(); for (let i = 0; i < strings.length; i++) { lengths[i] = encoder.encode(strings[i]).length; } return lengths; } export function concatenateBuffers(...buffers: Uint8Array[]): Uint8Array { const totalLength = buffers.reduce((sum, buf) => sum + buf.length, 0); const result = new Uint8Array(totalLength); let offset = 0; for (const buffer of buffers) { result.set(buffer, offset); offset += buffer.length; } return result; } ================================================ FILE: ts/src/encoding/fastPforEncoder.spec.ts ================================================ import { describe, expect, it } from "vitest"; import { decodeFastPforInt32 } from "../decoding/fastPforDecoder"; import { BLOCK_SIZE } from "../decoding/fastPforShared"; import { createFastPforEncoderWorkspace, encodeFastPforInt32WithWorkspace } from "./fastPforEncoder"; const GROWTH_MULTIPLIER = 3; const BASE_ALTERNATING_MASK = 1; const EXCEPTION_POS_A = 10; const EXCEPTION_POS_B = 100; const EXCEPTION_OUTLIER_VALUE = 7; const UNDERSIZED_PREALLOCATED_STREAM = 1; const LARGE_INCOMPRESSIBLE_BLOCK_COUNT = 2050; describe("FastPFOR encoder", () => { it("grows byteContainer when workspace capacity is too small", () => { const values = new Uint32Array(BLOCK_SIZE); for (let i = 0; i < values.length; i++) values[i] = i * GROWTH_MULTIPLIER; const workspace = createFastPforEncoderWorkspace(); workspace.byteContainer = new Uint8Array(0); const encoded = encodeFastPforInt32WithWorkspace(values, workspace); const decoded = decodeFastPforInt32(encoded, values.length); expect(decoded).toEqual(values); expect(workspace.byteContainer.length).toBeGreaterThan(0); }); it("grows exception buffer when preallocated exception stream is too small", () => { const values = new Uint32Array(BLOCK_SIZE); for (let i = 0; i < values.length; i++) values[i] = i & BASE_ALTERNATING_MASK; values[EXCEPTION_POS_A] = EXCEPTION_OUTLIER_VALUE; values[EXCEPTION_POS_B] = EXCEPTION_OUTLIER_VALUE; const workspace = createFastPforEncoderWorkspace(); workspace.dataToBePacked[2] = new Uint32Array(UNDERSIZED_PREALLOCATED_STREAM); const encoded = encodeFastPforInt32WithWorkspace(values, workspace); const decoded = decodeFastPforInt32(encoded, values.length); expect(decoded).toEqual(values); const exceptionStream = workspace.dataToBePacked[2]; expect(exceptionStream).toBeDefined(); expect(exceptionStream?.length).toBeGreaterThan(1); }); it("rounds grown exception buffers to a multiple of 32", () => { const values = new Uint32Array(BLOCK_SIZE); for (let i = 0; i < values.length; i++) values[i] = i & BASE_ALTERNATING_MASK; values[EXCEPTION_POS_A] = EXCEPTION_OUTLIER_VALUE; values[EXCEPTION_POS_B] = EXCEPTION_OUTLIER_VALUE; const workspace = createFastPforEncoderWorkspace(); workspace.dataToBePacked[2] = new Uint32Array(UNDERSIZED_PREALLOCATED_STREAM); encodeFastPforInt32WithWorkspace(values, workspace); const exceptionStream = workspace.dataToBePacked[2]; expect(exceptionStream).toBeDefined(); const exceptionStreamLength = exceptionStream?.length; expect(exceptionStreamLength).toBeDefined(); expect((exceptionStreamLength ?? 0) % 32).toBe(0); }); it("round-trips a large incompressible payload", () => { const values = new Uint32Array(BLOCK_SIZE * LARGE_INCOMPRESSIBLE_BLOCK_COUNT); for (let i = 0; i < values.length; i++) values[i] = -(i + 1); const encoded = encodeFastPforInt32WithWorkspace(values, createFastPforEncoderWorkspace()); const decoded = decodeFastPforInt32(encoded, values.length); expect(decoded).toEqual(values); }); }); ================================================ FILE: ts/src/encoding/fastPforEncoder.ts ================================================ import { MASKS, DEFAULT_PAGE_SIZE, BLOCK_SIZE, greatestMultiple, roundUpToMultipleOf32, normalizePageSize, } from "../decoding/fastPforShared"; const EXCEPTION_OVERHEAD_BITS = 8; const MAX_BIT_WIDTH = 32; const BIT_WIDTH_SLOTS = MAX_BIT_WIDTH + 1; const PAGE_SIZE = normalizePageSize(DEFAULT_PAGE_SIZE); const INITIAL_PACKED_BUFFER_SIZE_WORDS = (PAGE_SIZE / 32) * 4; const BYTE_CONTAINER_SIZE = ((3 * PAGE_SIZE) / BLOCK_SIZE + PAGE_SIZE) | 0; function requiredBits(value: number): number { return 32 - Math.clz32(value >>> 0); } function ensureInt32Capacity(buffer: Uint32Array, requiredLength: number): Uint32Array { if (requiredLength <= buffer.length) return buffer; let newLength = buffer.length === 0 ? 1 : buffer.length; while (newLength < requiredLength) { newLength *= 2; } const next = new Uint32Array(newLength); next.set(buffer); return next; } function ensureUint8Capacity(buffer: Uint8Array, requiredLength: number): Uint8Array { if (requiredLength <= buffer.length) return buffer; let newLength = buffer.length === 0 ? 1 : buffer.length; while (newLength < requiredLength) { newLength *= 2; } const next = new Uint8Array(newLength); next.set(buffer); return next; } /** * Internal workspace for the FastPFOR encoder. * Exposed so callers can avoid allocations. * Use one workspace per concurrent encode call. */ export type FastPforEncoderWorkspace = { dataToBePacked: Array; dataPointers: Int32Array; byteContainer: Uint8Array; bitWidthFrequencies: Int32Array; bestBitWidthPlan: Int32Array; }; export function fastPack32( inValues: Uint32Array, inPos: number, out: Uint32Array, outPos: number, bitWidth: number, ): void { if (bitWidth === 0) return; if (bitWidth === 32) { out.set(inValues.subarray(inPos, inPos + 32), outPos); return; } const mask = MASKS[bitWidth] >>> 0; let outputWordIndex = outPos; let bitOffset = 0; let currentWord = 0; for (let i = 0; i < 32; i++) { const value = (inValues[inPos + i] >>> 0) & mask; if (bitOffset + bitWidth <= 32) { currentWord |= value << bitOffset; bitOffset += bitWidth; if (bitOffset === 32) { out[outputWordIndex++] = currentWord | 0; bitOffset = 0; currentWord = 0; } } else { const lowBits = 32 - bitOffset; const lowMask = MASKS[lowBits] >>> 0; currentWord |= (value & lowMask) << bitOffset; out[outputWordIndex++] = currentWord | 0; currentWord = value >>> lowBits; bitOffset = bitWidth - lowBits; } } } export function createFastPforEncoderWorkspace(): FastPforEncoderWorkspace { const dataToBePacked: Array = new Array(BIT_WIDTH_SLOTS); for (let k = 1; k < BIT_WIDTH_SLOTS; k++) { dataToBePacked[k] = new Uint32Array(INITIAL_PACKED_BUFFER_SIZE_WORDS); } return { dataToBePacked, dataPointers: new Int32Array(BIT_WIDTH_SLOTS), byteContainer: new Uint8Array(BYTE_CONTAINER_SIZE), bitWidthFrequencies: new Int32Array(BIT_WIDTH_SLOTS), bestBitWidthPlan: new Int32Array(3), }; } function computeBestBitWidthPlan(inValues: Uint32Array, pos: number, workspace: FastPforEncoderWorkspace): void { const bitWidthFrequencies = workspace.bitWidthFrequencies; const bestBitWidthPlan = workspace.bestBitWidthPlan; bitWidthFrequencies.fill(0); for (let k = pos, kEnd = pos + BLOCK_SIZE; k < kEnd; k++) { bitWidthFrequencies[requiredBits(inValues[k])]++; } let maxBitWidth = MAX_BIT_WIDTH; while (bitWidthFrequencies[maxBitWidth] === 0) maxBitWidth--; let bestBitWidth = maxBitWidth; let bestCost = maxBitWidth * BLOCK_SIZE; let exceptionCount = 0; let bestExceptionCount = exceptionCount; for (let candidateBitWidth = maxBitWidth - 1; candidateBitWidth >= 0; candidateBitWidth--) { exceptionCount += bitWidthFrequencies[candidateBitWidth + 1]; if (exceptionCount === BLOCK_SIZE) break; let candidateCost = exceptionCount * EXCEPTION_OVERHEAD_BITS + exceptionCount * (maxBitWidth - candidateBitWidth) + candidateBitWidth * BLOCK_SIZE + 8; if (maxBitWidth - candidateBitWidth === 1) candidateCost -= exceptionCount; if (candidateCost < bestCost) { bestCost = candidateCost; bestBitWidth = candidateBitWidth; bestExceptionCount = exceptionCount; } } bestBitWidthPlan[0] = bestBitWidth; bestBitWidthPlan[1] = bestExceptionCount; bestBitWidthPlan[2] = maxBitWidth; } function writeByte(workspace: FastPforEncoderWorkspace, byteContainerPos: number, byteValue: number): number { if (byteContainerPos >= workspace.byteContainer.length) { workspace.byteContainer = ensureUint8Capacity(workspace.byteContainer, byteContainerPos + 1); } workspace.byteContainer[byteContainerPos] = byteValue & 0xff; return byteContainerPos + 1; } function ensureExceptionValuesCapacity( dataToBePacked: Array, dataPointers: Int32Array, exceptionBitWidth: number, exceptionCount: number, ): void { if (exceptionBitWidth === 1) return; const needed = dataPointers[exceptionBitWidth] + exceptionCount; const currentExceptionValues = dataToBePacked[exceptionBitWidth]; if (!currentExceptionValues || needed >= currentExceptionValues.length) { let newSize = 2 * needed; newSize = roundUpToMultipleOf32(newSize); const next = new Uint32Array(newSize); if (currentExceptionValues) next.set(currentExceptionValues); dataToBePacked[exceptionBitWidth] = next; } } function writeBlockHeader( workspace: FastPforEncoderWorkspace, byteContainerPos: number, bitWidth: number, exceptionCount: number, maxBitWidth: number, ): number { byteContainerPos = writeByte(workspace, byteContainerPos, bitWidth); byteContainerPos = writeByte(workspace, byteContainerPos, exceptionCount); if (exceptionCount > 0) { byteContainerPos = writeByte(workspace, byteContainerPos, maxBitWidth); } return byteContainerPos; } function recordBlockExceptions( workspace: FastPforEncoderWorkspace, inValues: Uint32Array, blockPos: number, bitWidth: number, exceptionCount: number, exceptionBitWidth: number, byteContainerPos: number, ): number { if (exceptionCount === 0) return byteContainerPos; const dataToBePacked = workspace.dataToBePacked; const dataPointers = workspace.dataPointers; ensureExceptionValuesCapacity(dataToBePacked, dataPointers, exceptionBitWidth, exceptionCount); for (let k = 0; k < BLOCK_SIZE; k++) { const value = inValues[blockPos + k] >>> 0; if (value >>> bitWidth !== 0) { byteContainerPos = writeByte(workspace, byteContainerPos, k); if (exceptionBitWidth !== 1) { const exceptionValues = dataToBePacked[exceptionBitWidth]; exceptionValues[dataPointers[exceptionBitWidth]++] = (value >>> bitWidth) | 0; } } } return byteContainerPos; } type EncodeState = { inPos: number; out: Uint32Array; outPos: number }; function packBlock(inValues: Uint32Array, blockPos: number, bitWidth: number, state: EncodeState): void { for (let k = 0; k < BLOCK_SIZE; k += 32) { state.out = ensureInt32Capacity(state.out, state.outPos + bitWidth); fastPack32(inValues, blockPos + k, state.out, state.outPos, bitWidth); state.outPos += bitWidth; } } function padByteContainerToInt32(workspace: FastPforEncoderWorkspace, byteContainerPos: number): number { while ((byteContainerPos & 3) !== 0) { byteContainerPos = writeByte(workspace, byteContainerPos, 0); } return byteContainerPos; } function writeByteContainerInts( workspace: FastPforEncoderWorkspace, state: EncodeState, byteContainerPos: number, ): void { const howManyInts = byteContainerPos / 4; state.out = ensureInt32Capacity(state.out, state.outPos + howManyInts); const byteContainer = workspace.byteContainer; for (let i = 0; i < howManyInts; i++) { const base = i * 4; const packedWord = byteContainer[base] | (byteContainer[base + 1] << 8) | (byteContainer[base + 2] << 16) | (byteContainer[base + 3] << 24) | 0; state.out[state.outPos + i] = packedWord; } state.outPos += howManyInts; } function computeExceptionBitmap(dataPointers: Int32Array): number { let bitmap = 0; for (let k = 2; k <= MAX_BIT_WIDTH; k++) { if (dataPointers[k] !== 0) { bitmap |= k === MAX_BIT_WIDTH ? 0x80000000 : 1 << (k - 1); } } return bitmap; } function writeExceptionStreams(workspace: FastPforEncoderWorkspace, state: EncodeState): void { const dataPointers = workspace.dataPointers; const dataToBePacked = workspace.dataToBePacked; const bitmap = computeExceptionBitmap(dataPointers); state.out = ensureInt32Capacity(state.out, state.outPos + 1); state.out[state.outPos++] = bitmap; for (let k = 2; k <= MAX_BIT_WIDTH; k++) { const size = dataPointers[k]; if (size !== 0) { state.out = ensureInt32Capacity(state.out, state.outPos + 1); state.out[state.outPos++] = size | 0; let j = 0; for (; j < size; j += 32) { const exceptionValues = dataToBePacked[k]; state.out = ensureInt32Capacity(state.out, state.outPos + k); fastPack32(exceptionValues, j, state.out, state.outPos, k); state.outPos += k; } const overflow = j - size; state.outPos -= (overflow * k) >>> 5; } } } function encodePage( inValues: Uint32Array, thisSize: number, state: EncodeState, workspace: FastPforEncoderWorkspace, ): void { const headerPos = state.outPos; state.out = ensureInt32Capacity(state.out, headerPos + 1); state.outPos = (state.outPos + 1) | 0; const dataPointers = workspace.dataPointers; dataPointers.fill(0); let byteContainerPos = 0; let tmpInPos = state.inPos; const finalInPos = tmpInPos + thisSize - BLOCK_SIZE; for (; tmpInPos <= finalInPos; tmpInPos += BLOCK_SIZE) { computeBestBitWidthPlan(inValues, tmpInPos, workspace); const bestBitWidthPlan = workspace.bestBitWidthPlan; const bitWidth = bestBitWidthPlan[0]; const exceptionCount = bestBitWidthPlan[1]; const maxBitWidth = bestBitWidthPlan[2]; const exceptionBitWidth = exceptionCount > 0 ? maxBitWidth - bitWidth : 0; byteContainerPos = writeBlockHeader(workspace, byteContainerPos, bitWidth, exceptionCount, maxBitWidth); byteContainerPos = recordBlockExceptions( workspace, inValues, tmpInPos, bitWidth, exceptionCount, exceptionBitWidth, byteContainerPos, ); packBlock(inValues, tmpInPos, bitWidth, state); } const pageEndOutPos = state.outPos; state.inPos = tmpInPos; state.out[headerPos] = (pageEndOutPos - headerPos) | 0; const byteSize = byteContainerPos; byteContainerPos = padByteContainerToInt32(workspace, byteContainerPos); state.out = ensureInt32Capacity(state.out, state.outPos + 1); state.out[state.outPos++] = byteSize | 0; writeByteContainerInts(workspace, state, byteContainerPos); writeExceptionStreams(workspace, state); } function encodeAlignedPages( inValues: Uint32Array, inLength: number, state: EncodeState, workspace: FastPforEncoderWorkspace, ): void { const alignedLength = greatestMultiple(inLength, BLOCK_SIZE); const finalInPos = state.inPos + alignedLength; while (state.inPos !== finalInPos) { const thisSize = Math.min(PAGE_SIZE, finalInPos - state.inPos); encodePage(inValues, thisSize, state, workspace); } } function encode( inValues: Uint32Array, inLength: number, state: EncodeState, workspace: FastPforEncoderWorkspace, ): void { const alignedLength = greatestMultiple(inLength, BLOCK_SIZE); state.out = ensureInt32Capacity(state.out, state.outPos + 1); state.out[state.outPos++] = alignedLength; if (alignedLength === 0) return; encodeAlignedPages(inValues, alignedLength, state, workspace); } /** * VByte encoding for FastPFOR tail values (MSB=1 terminator). * Note: Inverts standard Protobuf Varint (MSB=0 terminator), so we cannot reuse generic methods. */ function encodeVByte( inValues: Uint32Array, inLength: number, state: EncodeState, workspace: FastPforEncoderWorkspace, ): void { if (inLength === 0) return; const requiredBytes = inLength * 5 + 3; workspace.byteContainer = ensureUint8Capacity(workspace.byteContainer, requiredBytes); const start = state.inPos; let bytePos = 0; for (let k = start; k < start + inLength; k++) { let value = inValues[k] >>> 0; while (value >= 0x80) { workspace.byteContainer[bytePos++] = value & 0x7f; value >>>= 7; } workspace.byteContainer[bytePos++] = (value | 0x80) & 0xff; } while ((bytePos & 3) !== 0) workspace.byteContainer[bytePos++] = 0; const intsToWrite = bytePos / 4; state.out = ensureInt32Capacity(state.out, state.outPos + intsToWrite); let outIdx = state.outPos; for (let i = 0; i < bytePos; i += 4) { const packedWord = workspace.byteContainer[i] | (workspace.byteContainer[i + 1] << 8) | (workspace.byteContainer[i + 2] << 16) | (workspace.byteContainer[i + 3] << 24) | 0; state.out[outIdx++] = packedWord; } state.outPos = outIdx; state.inPos = (state.inPos + inLength) | 0; } /** * Encodes an int32 stream using the FastPFOR wire format (pages + VByte tail). */ export function encodeFastPforInt32WithWorkspace( values: Uint32Array, workspace: FastPforEncoderWorkspace, ): Uint32Array { const state: EncodeState = { inPos: 0, outPos: 0, out: new Uint32Array(values.length + 1024) }; encode(values, values.length, state, workspace); const remaining = values.length - state.inPos; encodeVByte(values, remaining, state, workspace); return state.out.subarray(0, state.outPos); } ================================================ FILE: ts/src/encoding/fsstEncoder.ts ================================================ /** * Create symbol table from string array * * @param symbolStrings Array of symbol strings * @returns Symbol table buffer and lengths */ export function createSymbolTable(symbolStrings: string[]): { symbols: Uint8Array; symbolLengths: Uint32Array } { const textEncoder = new TextEncoder(); const symbolBuffers = symbolStrings.map((s) => textEncoder.encode(s)); const symbolLengths = new Uint32Array(symbolBuffers.map((b) => b.length)); const totalLength = symbolBuffers.reduce((sum, b) => sum + b.length, 0); const symbols = new Uint8Array(totalLength); let offset = 0; for (const buffer of symbolBuffers) { symbols.set(buffer, offset); offset += buffer.length; } return { symbols, symbolLengths }; } /** * Encode data using FSST compression with pre-defined symbol table * Encoder requires pre-defined symbol table. Real FSST learns optimal symbols from data. This * implementation is for testing decoder only. * * @param symbols Array of symbols, where each symbol can be between 1 and 8 bytes * @param symbolLengths Array of symbol lengths, length of each symbol in symbols array * @param uncompressedData Data to compress * @returns FSST compressed data, where each entry is an index to the symbols array */ export function encodeFsst(symbols: Uint8Array, symbolLengths: Uint32Array, uncompressedData: Uint8Array): Uint8Array { if (uncompressedData.length === 0) { return new Uint8Array(0); } // Calculate symbol offsets (cumulative sum of lengths) const symbolOffsets: number[] = new Array(symbolLengths.length).fill(0); for (let i = 1; i < symbolLengths.length; i++) { symbolOffsets[i] = symbolOffsets[i - 1] + symbolLengths[i - 1]; } const result: number[] = []; let pos = 0; while (pos < uncompressedData.length) { let bestSymbolIndex = -1; let bestSymbolLength = 0; // Try to find longest matching symbol at current position for (let symbolIndex = 0; symbolIndex < symbolLengths.length; symbolIndex++) { const symbolLength = symbolLengths[symbolIndex]; const symbolOffset = symbolOffsets[symbolIndex]; // Check if symbol could fit and is longer than current best if (pos + symbolLength <= uncompressedData.length && symbolLength > bestSymbolLength) { // Check if bytes match let matches = true; for (let i = 0; i < symbolLength; i++) { if (symbols[symbolOffset + i] !== uncompressedData[pos + i]) { matches = false; break; } } if (matches) { bestSymbolIndex = symbolIndex; bestSymbolLength = symbolLength; } } } if (bestSymbolIndex !== -1) { // Found a matching symbol result.push(bestSymbolIndex); pos += bestSymbolLength; } else { // No match - emit escape sequence (255 followed by literal byte) result.push(255); result.push(uncompressedData[pos]); pos++; } } return new Uint8Array(result); } ================================================ FILE: ts/src/encoding/integerEncodingUtils.ts ================================================ import IntWrapper from "../decoding/intWrapper"; import { createFastPforEncoderWorkspace, encodeFastPforInt32WithWorkspace } from "./fastPforEncoder"; import { encodeBigEndianInt32s } from "./bigEndianEncode"; export function encodeVarintInt32Value(value: number, dst: Uint8Array, offset: IntWrapper): void { let v = value; while (v > 0x7f) { dst[offset.get()] = (v & 0x7f) | 0x80; offset.increment(); v >>>= 7; } dst[offset.get()] = v & 0x7f; offset.increment(); } export function encodeVarintInt32(values: Uint32Array): Uint8Array { const buffer = new Uint8Array(values.length * 5); const offset = new IntWrapper(0); for (const value of values) { encodeVarintInt32Value(value, buffer, offset); } return buffer.slice(0, offset.get()); } export function encodeVarintInt64(values: BigUint64Array): Uint8Array { const buffer = new Uint8Array(values.length * 10); const offset = new IntWrapper(0); for (const value of values) { encodeVarintInt64Value(value, buffer, offset); } return buffer.slice(0, offset.get()); } function encodeVarintInt64Value(value: bigint, dst: Uint8Array, offset: IntWrapper): void { let v = value; while (v > 0x7fn) { dst[offset.get()] = Number(v & 0x7fn) | 0x80; offset.increment(); v >>= 7n; } dst[offset.get()] = Number(v & 0x7fn); offset.increment(); } export function encodeVarintFloat64(values: Float64Array): Uint8Array { // 1. Calculate the exact size required for the buffer let size = 0; for (let i = 0; i < values.length; i++) { let val = values[i]; // Ensure we handle the value as a positive integer val = val < 0 ? 0 : Math.floor(val); // 0 always takes 1 byte if (val === 0) { size++; continue; } // Calculate bytes needed: ceil(log128(val + 1)) while (val > 0) { size++; val = Math.floor(val / 128); } } const dst = new Uint8Array(size); const offset = new IntWrapper(0); for (let i = 0; i < values.length; i++) { encodeVarintFloat64Value(values[i], dst, offset); } return dst; } /** * Encodes a single number into the buffer at the given offset using Varint encoding. * Handles numbers up to 2^53 (MAX_SAFE_INTEGER) correctly. */ function encodeVarintFloat64Value(val: number, buf: Uint8Array, offset: IntWrapper): void { // Ensure integer val = Math.floor(val); // Handle 0 explicitly or ensure loop runs once if (val === 0) { buf[offset.get()] = 0; offset.increment(); return; } while (val >= 128) { // Write 7 bits of data | 0x80 (continuation bit) buf[offset.get()] = (val % 128) | 0x80; offset.increment(); // Shift right by 7 bits val = Math.floor(val / 128); } // Write the last byte (no continuation bit) buf[offset.get()] = val; offset.increment(); } export function encodeFastPfor(values: Uint32Array): Uint8Array { const encoderWorkspace = createFastPforEncoderWorkspace(); const encodedWords = encodeFastPforInt32WithWorkspace(values, encoderWorkspace); return encodeBigEndianInt32s(encodedWords); } export function encodeZigZagInt32Value(value: number): number { return (value << 1) ^ (value >> 31); } export function encodeZigZagInt64Value(value: bigint): bigint { return (value << 1n) ^ (value >> 63n); } export function encodeZigZagFloat64Value(n: number): number { return n >= 0 ? n * 2 : n * -2 - 1; } export function encodeZigZagInt32(data: Int32Array): Uint32Array { const result = new Uint32Array(data.length); for (let i = 0; i < data.length; i++) { result[i] = encodeZigZagInt32Value(data[i]); } return result; } export function encodeZigZagInt64(data: BigInt64Array): BigUint64Array { const result = new BigUint64Array(data.length); for (let i = 0; i < data.length; i++) { result[i] = encodeZigZagInt64Value(data[i]); } return result; } export function encodeZigZagFloat64(data: Float64Array): void { for (let i = 0; i < data.length; i++) { data[i] = encodeZigZagFloat64Value(data[i]); } } export function encodeUnsignedRleInt32(input: Uint32Array): { data: Uint32Array; runs: number } { if (input.length === 0) { return { data: new Uint32Array(0), runs: 0 }; } const runLengths: number[] = []; const runValues: number[] = []; let currentRunLength = 0; let currentValue = input[0]; for (let i = 0; i < input.length; i++) { const nextValue = input[i]; if (nextValue === currentValue) { currentRunLength++; } else { // End of the current run, record it runLengths.push(currentRunLength); runValues.push(currentValue); // Start a new run currentValue = nextValue; currentRunLength = 1; } } // Record the final run after the loop finishes runLengths.push(currentRunLength); runValues.push(currentValue); // Combine lengths and values into the final structured output array const numRuns = runLengths.length; const encodedData = new Uint32Array(numRuns * 2); // Populate the first half with lengths encodedData.set(runLengths, 0); // Populate the second half with values, offset by the total number of runs encodedData.set(runValues, numRuns); return { data: encodedData, runs: numRuns }; } export function encodeUnsignedRleInt64(input: BigInt64Array): { data: BigUint64Array; runs: number } { if (input.length === 0) { return { data: new BigUint64Array(0), runs: 0 }; } const runLengths: number[] = []; const runValues: bigint[] = []; let currentRunLength = 0; let currentValue: bigint = input[0]; for (let i = 0; i < input.length; i++) { const nextValue = input[i]; if (nextValue === currentValue) { currentRunLength++; } else { // End of the current run, record it runLengths.push(currentRunLength); runValues.push(currentValue); // Start a new run currentValue = nextValue; currentRunLength = 1; } } // Record the final run after the loop finishes runLengths.push(currentRunLength); runValues.push(currentValue); // Combine lengths and values into the final structured output array (BigUint64Array) const numRuns = runLengths.length; const encodedData = new BigUint64Array(numRuns * 2); // Populate the first half with lengths, converting the run length numbers to bigint for storage in the BigUint64Array. for (let i = 0; i < numRuns; i++) { encodedData[i] = BigInt(runLengths[i]); } // Populate the second half with values, offset by the total number of runs encodedData.set(runValues, numRuns); return { data: encodedData, runs: numRuns }; } export function encodeUnsignedRleFloat64(input: Float64Array): { data: Float64Array; runs: number } { if (input.length === 0) { return { data: new Float64Array(0), runs: 0 }; } const runLengths: number[] = []; const runValues: number[] = []; let currentRunLength = 0; let currentValue = input[0]; for (let i = 0; i < input.length; i++) { const nextValue = input[i]; if (nextValue === currentValue) { currentRunLength++; } else { // End of the current run, record it runLengths.push(currentRunLength); runValues.push(currentValue); // Start a new run currentValue = nextValue; currentRunLength = 1; } } // Record the final run after the loop finishes runLengths.push(currentRunLength); runValues.push(currentValue); // Combine lengths and values into the final structured output array (Float64Array) const numRuns = runLengths.length; // The final array is twice the size of the number of runs const encodedData = new Float64Array(numRuns * 2); // Populate the first half with lengths encodedData.set(runLengths, 0); // Populate the second half with values, offset by the total number of runs encodedData.set(runValues, numRuns); return { data: encodedData, runs: numRuns }; } export function encodeZigZagDeltaInt32(data: Int32Array): Uint32Array { if (data.length === 0) { return new Uint32Array(0); } const encodedData = new Uint32Array(data.length); let previousValue = data[0]; encodedData[0] = encodeZigZagInt32Value(previousValue); for (let i = 1; i < data.length; i++) { const currentValue = data[i]; const delta = currentValue - previousValue; const encodedDelta = encodeZigZagInt32Value(delta); // Store the encoded delta back into the array encodedData[i] = encodedDelta; // Update the previous value tracker for the next iteration's delta calculation previousValue = currentValue; } return encodedData; } export function encodeZigZagDeltaInt64(data: BigInt64Array): BigUint64Array { if (data.length === 0) { return new BigUint64Array(0); } const encodedData = new BigUint64Array(data.length); let previousValue = data[0]; encodedData[0] = encodeZigZagInt64Value(previousValue); for (let i = 1; i < data.length; i++) { const currentValue = data[i]; const delta = currentValue - previousValue; const encodedDelta = encodeZigZagInt64Value(delta); // Store the encoded delta back into the array encodedData[i] = encodedDelta; // Update the previous value tracker for the next iteration's delta calculation previousValue = currentValue; } return encodedData; } export function encodeZigZagDeltaFloat64(data: Float64Array): void { if (data.length === 0) { return; } let previousValue = data[0]; data[0] = encodeZigZagFloat64Value(previousValue); for (let i = 1; i < data.length; i++) { const currentValue = data[i]; const delta = currentValue - previousValue; const encodedDelta = encodeZigZagFloat64Value(delta); // Store the encoded delta back into the array data[i] = encodedDelta; // Update the previous value tracker for the next iteration's delta calculation previousValue = currentValue; } } export function encodeZigZagRleInt32(input: Int32Array): { data: Uint32Array; runs: number; numTotalValues: number; } { if (input.length === 0) { return { data: new Uint32Array(0), runs: 0, numTotalValues: 0 }; } const zigzagEncodedStream: number[] = []; // Step 1: Apply Zigzag Encoding to all values for (let i = 0; i < input.length; i++) { zigzagEncodedStream.push(encodeZigZagInt32Value(input[i])); } // zigzagEncodedStream now holds the intermediate stream of zigzag values // Step 2: Apply RLE to the stream of zigzag-encoded values const runLengths: number[] = []; const runZigZagValues: number[] = []; let currentRunLength = 0; let currentValue = zigzagEncodedStream[0]; for (let i = 0; i < zigzagEncodedStream.length; i++) { const nextValue = zigzagEncodedStream[i]; if (nextValue === currentValue) { currentRunLength++; } else { runLengths.push(currentRunLength); runZigZagValues.push(currentValue); currentValue = nextValue; currentRunLength = 1; } } // Record the final run runLengths.push(currentRunLength); runZigZagValues.push(currentValue); // Step 3: Combine lengths and values into the final structured output array const numRuns = runLengths.length; // The final array uses Uint32Array for lengths AND values const encodedData = new Uint32Array(numRuns * 2); // Populate the first half with lengths encodedData.set(runLengths, 0); // Populate the second half with zigzagged values encodedData.set(runZigZagValues, numRuns); return { data: encodedData, runs: numRuns, numTotalValues: input.length, // Total original values count }; } export function encodeZigZagRleInt64(input: BigInt64Array): { data: BigUint64Array; runs: number; numTotalValues: number; } { if (input.length === 0) { return { data: new BigUint64Array(0), runs: 0, numTotalValues: 0 }; } const zigzagEncodedStream: bigint[] = []; // Step 1: Apply Zigzag Encoding to all values for (let i = 0; i < input.length; i++) { zigzagEncodedStream.push(encodeZigZagInt64Value(input[i])); } // zigzagEncodedStream now holds the intermediate stream of zigzag values // Step 2: Apply RLE to the stream of zigzag-encoded values const runLengths: number[] = []; const runZigZagValues: bigint[] = []; let currentRunLength = 0; let currentValue: bigint = zigzagEncodedStream[0]; for (let i = 0; i < zigzagEncodedStream.length; i++) { const nextValue = zigzagEncodedStream[i]; if (nextValue === currentValue) { currentRunLength++; } else { runLengths.push(currentRunLength); runZigZagValues.push(currentValue); currentValue = nextValue; currentRunLength = 1; } } // Record the final run runLengths.push(currentRunLength); runZigZagValues.push(currentValue); // Step 3: Combine lengths and values into the final structured output array const numRuns = runLengths.length; // The final array uses BigUint64Array for lengths AND values const encodedData = new BigUint64Array(numRuns * 2); // Populate the first half with lengths (converting numbers back to BigUint64Array format) for (let i = 0; i < numRuns; i++) { encodedData[i] = BigInt(runLengths[i]); } // Populate the second half with zigzagged values encodedData.set(runZigZagValues, numRuns); return { data: encodedData, runs: numRuns, numTotalValues: input.length, // Total original values count }; } export function encodeZigZagRleFloat64(input: Float64Array): { data: Float64Array; runs: number; numTotalValues: number; } { if (input.length === 0) { return { data: new Float64Array(0), runs: 0, numTotalValues: 0 }; } const zigzagEncodedStream: number[] = []; // Step 1: Apply Float-based Zigzag Encoding to all values for (let i = 0; i < input.length; i++) { zigzagEncodedStream.push(encodeZigZagFloat64Value(input[i])); } // zigzagEncodedStream now holds the intermediate stream of zigzag values (as floats acting as integers) // Step 2: Apply RLE to the stream of zigzag-encoded values const runLengths: number[] = []; const runZigZagValues: number[] = []; let currentRunLength = 0; let currentValue = zigzagEncodedStream[0]; for (let i = 0; i < zigzagEncodedStream.length; i++) { const nextValue = zigzagEncodedStream[i]; if (nextValue === currentValue) { currentRunLength++; } else { runLengths.push(currentRunLength); runZigZagValues.push(currentValue); currentValue = nextValue; currentRunLength = 1; } } // Record the final run runLengths.push(currentRunLength); runZigZagValues.push(currentValue); // Step 3: Combine lengths and values into the final structured output array const numRuns = runLengths.length; // The final array uses Float64Array for lengths AND values const encodedData = new Float64Array(numRuns * 2); // Populate the first half with lengths encodedData.set(runLengths, 0); // Populate the second half with zigzagged values encodedData.set(runZigZagValues, numRuns); return { data: encodedData, runs: numRuns, numTotalValues: input.length, // Total original values count }; } /** * This is not really a encode, but more of a decode method... */ export function encodeDeltaInt32(data: Int32Array | Uint32Array): void { if (data.length === 0) { return; } for (let i = data.length - 1; i >= 1; i--) { data[i] = data[i] - data[i - 1]; } } export function encodeComponentwiseDeltaVec2(data: Int32Array): Uint32Array { if (data.length < 2) return new Uint32Array(data); const encoded = new Uint32Array(data.length); // Reverse iterate to avoid overwriting data needed for delta computation for (let i = data.length - 2; i >= 2; i -= 2) { const deltaX = data[i] - data[i - 2]; const deltaY = data[i + 1] - data[i - 1]; encoded[i] = encodeZigZagInt32Value(deltaX); encoded[i + 1] = encodeZigZagInt32Value(deltaY); } // Encode first vertex last (after computing all deltas) encoded[0] = encodeZigZagInt32Value(data[0]); encoded[1] = encodeZigZagInt32Value(data[1]); return encoded; } export function encodeComponentwiseDeltaVec2Scaled(data: Int32Array, scale: number): Uint32Array { if (data.length < 2) return new Uint32Array(data); const encoded = new Uint32Array(data.length); // First, inverse scale all values (tile space -> original space) for (let i = 0; i < data.length; i++) { encoded[i] = Math.round(data[i] / scale); } // Then apply componentwise delta encoding (same as non-scaled version) // Reverse iterate to avoid overwriting data needed for delta computation for (let i = encoded.length - 2; i >= 2; i -= 2) { const deltaX = encoded[i] - encoded[i - 2]; const deltaY = encoded[i + 1] - encoded[i - 1]; encoded[i] = encodeZigZagInt32Value(deltaX); encoded[i + 1] = encodeZigZagInt32Value(deltaY); } // Encode first vertex last (after computing all deltas) encoded[0] = encodeZigZagInt32Value(encoded[0]); encoded[1] = encodeZigZagInt32Value(encoded[1]); return encoded; } // HM TODO: // zigZagDeltaOfDeltaDecoding export function encodeZigZagRleDeltaInt32(values: Int32Array | number[]): { data: Uint32Array; runs: number; numTotalValues: number; } { if (values.length === 0) { return { data: new Uint32Array(0), runs: 0, numTotalValues: 0 }; } const runLengths: number[] = []; const encodedDeltas: number[] = []; // The decoder explicitly sets decodedValues[0] = 0 and uses previousValue = 0. // Therefore, we initialize our 'previous' tracker to 0 to calculate the first delta correctly. let previousValue = 0; // Variables to track the current run let currentDelta: number | null = null; let currentRunLength = 0; for (let i = 0; i < values.length; i++) { const value = values[i]; const delta = value - previousValue; previousValue = value; if (currentDelta === null) { // First element initialization currentDelta = delta; currentRunLength = 1; } else if (delta === currentDelta) { // Continuation of the current run currentRunLength++; } else { // The run has broken (delta changed) // 1. Push the length of the previous run runLengths.push(currentRunLength); // 2. ZigZag encode the previous delta and push it encodedDeltas.push(encodeZigZagInt32Value(currentDelta)); // Start the new run currentDelta = delta; currentRunLength = 1; } } // Flush the final run remaining after the loop finishes if (currentDelta !== null) { runLengths.push(currentRunLength); encodedDeltas.push(encodeZigZagInt32Value(currentDelta)); } const numRuns = runLengths.length; // The decoder expects 'data' to be: [RunLength 1, RunLength 2... | Value 1, Value 2...] // Size is numRuns * 2 (First half lengths, second half values) const data = new Uint32Array(numRuns * 2); for (let i = 0; i < numRuns; i++) { data[i] = runLengths[i]; // First half: Run Lengths data[i + numRuns] = encodedDeltas[i]; // Second half: ZigZag Encoded Deltas } return { data: data, runs: numRuns, numTotalValues: values.length, }; } export function encodeRleDeltaInt32(values: Uint32Array | number[]): { data: Uint32Array; runs: number; numTotalValues: number; } { if (values.length === 0) { return { data: new Uint32Array(0), runs: 0, numTotalValues: 0 }; } const runLengths: number[] = []; const deltas: number[] = []; // The decoder logic relies on: decodedValues[0] = 0; previousValue = 0; // So the encoder must assume the sequence starts relative to 0. let previousValue = 0; // Track the current run of deltas let currentDelta: number | null = null; let currentRunLength = 0; for (let i = 0; i < values.length; i++) { const value = values[i]; const delta = value - previousValue; previousValue = value; if (currentDelta === null) { // Initialize first run currentDelta = delta; currentRunLength = 1; } else if (delta === currentDelta) { // Continue current run currentRunLength++; } else { // Delta changed: flush the previous run runLengths.push(currentRunLength); deltas.push(currentDelta); // Start new run currentDelta = delta; currentRunLength = 1; } } // Flush the final run if (currentDelta !== null) { runLengths.push(currentRunLength); deltas.push(currentDelta); } const numRuns = runLengths.length; // Pack into Uint32Array: [ RunLength 1...N | Delta 1...N ] const data = new Uint32Array(numRuns * 2); for (let i = 0; i < numRuns; i++) { data[i] = runLengths[i]; data[i + numRuns] = deltas[i]; } return { data: data, runs: numRuns, numTotalValues: values.length, }; } export function encodeDeltaRleInt32(input: Int32Array): { data: Uint32Array; runs: number; numValues: number; } { if (input.length === 0) { return { data: new Uint32Array(0), runs: 0, numValues: 0 }; } const deltasAndEncoded: number[] = []; let previousValue = 0; // Step 1 & 2: Calculate Deltas and Zigzag Encode them for (let i = 0; i < input.length; i++) { const currentValue = input[i]; const delta = currentValue - previousValue; const encodedDelta = encodeZigZagInt32Value(delta); deltasAndEncoded.push(encodedDelta); previousValue = currentValue; } // deltasAndEncoded now holds the intermediate stream of zigzagged deltas // Step 3: Apply RLE to the stream of zigzag-encoded deltas const runLengths: number[] = []; const runZigZagDeltas: number[] = []; let currentRunLength = 0; let currentRunValue = deltasAndEncoded[0]; for (let i = 0; i < deltasAndEncoded.length; i++) { const nextValue = deltasAndEncoded[i]; if (nextValue === currentRunValue) { currentRunLength++; } else { runLengths.push(currentRunLength); runZigZagDeltas.push(currentRunValue); currentRunValue = nextValue; currentRunLength = 1; } } // Record the final run runLengths.push(currentRunLength); runZigZagDeltas.push(currentRunValue); // Step 4: Combine lengths and values into the final structured output array const numRuns = runLengths.length; const encodedData = new Uint32Array(numRuns * 2); // Populate the first half with lengths for (let i = 0; i < numRuns; i++) { encodedData[i] = runLengths[i]; } // Populate the second half with zigzagged deltas // Uint32Array.set() works with standard number arrays encodedData.set(runZigZagDeltas, numRuns); return { data: encodedData, runs: numRuns, numValues: input.length, // Total original values count }; } export function encodeDeltaRleInt64(input: BigInt64Array): { data: BigUint64Array; runs: number; numValues: number; } { if (input.length === 0) { return { data: new BigUint64Array(0), runs: 0, numValues: 0 }; } const deltasAndEncoded: bigint[] = []; let previousValue = 0n; // Step 1 & 2: Calculate Deltas and Zigzag Encode them for (let i = 0; i < input.length; i++) { const currentValue = input[i]; const delta = currentValue - previousValue; const encodedDelta = encodeZigZagInt64Value(delta); deltasAndEncoded.push(encodedDelta); previousValue = currentValue; } // deltasAndEncoded now holds the intermediate stream of zigzagged deltas // Step 3: Apply RLE to the stream of zigzag-encoded deltas const runLengths: number[] = []; const runZigZagDeltas: bigint[] = []; let currentRunLength = 0; let currentValue = deltasAndEncoded[0]; for (let i = 0; i < deltasAndEncoded.length; i++) { const nextValue = deltasAndEncoded[i]; if (nextValue === currentValue) { currentRunLength++; } else { runLengths.push(currentRunLength); runZigZagDeltas.push(currentValue); currentValue = nextValue; currentRunLength = 1; } } // Record the final run runLengths.push(currentRunLength); runZigZagDeltas.push(currentValue); // Step 4: Combine lengths and values into the final structured output array const numRuns = runLengths.length; const encodedData = new BigUint64Array(numRuns * 2); // Populate the first half with lengths (converting numbers back to BigUint64Array for storage) for (let i = 0; i < numRuns; i++) { encodedData[i] = BigInt(runLengths[i]); } // Populate the second half with zigzagged deltas encodedData.set(runZigZagDeltas, numRuns); return { data: encodedData, runs: numRuns, numValues: input.length, // Total original values count }; } ================================================ FILE: ts/src/encoding/integerStreamEncoder.ts ================================================ import type { StreamMetadata } from "../metadata/tile/streamMetadataDecoder"; import { LogicalLevelTechnique } from "../metadata/tile/logicalLevelTechnique"; import { encodeDeltaRleInt32, encodeZigZagInt32, encodeZigZagRleInt32, encodeUnsignedRleInt32, encodeDeltaInt32, encodeUnsignedRleFloat64, encodeZigZagDeltaFloat64, encodeZigZagFloat64, encodeZigZagRleFloat64, encodeVarintInt32, encodeVarintInt64, encodeZigZagInt64Value, encodeFastPfor, encodeComponentwiseDeltaVec2, encodeComponentwiseDeltaVec2Scaled, encodeZigZagDeltaInt32, } from "./integerEncodingUtils"; import type BitVector from "../vector/flat/bitVector"; import { packNullable } from "./packNullableUtils"; import { PhysicalLevelTechnique } from "../metadata/tile/physicalLevelTechnique"; import type GeometryScaling from "../decoding/geometryScaling"; export function encodeSignedInt32Stream( values: Int32Array, metadata: StreamMetadata, bitVector?: BitVector, scalingData?: GeometryScaling, ): Uint8Array { const { data } = encodeSignedInt32(values, metadata, bitVector, scalingData); return encodePhysicalLevelTechnique(data, metadata); } export function encodeUnsignedInt32Stream( values: Uint32Array, metadata: StreamMetadata, bitVector?: BitVector, scalingData?: GeometryScaling, ): Uint8Array { const { data } = encodeUnsignedInt32(values, metadata, bitVector, scalingData); return encodePhysicalLevelTechnique(data, metadata); } function encodePhysicalLevelTechnique(data: Uint32Array, streamMetadata: StreamMetadata): Uint8Array { const physicalLevelTechnique = streamMetadata.physicalLevelTechnique; if (physicalLevelTechnique === PhysicalLevelTechnique.FAST_PFOR) { return encodeFastPfor(data); } if (physicalLevelTechnique === PhysicalLevelTechnique.VARINT) { return encodeVarintInt32(data); } if (physicalLevelTechnique === PhysicalLevelTechnique.NONE) { const slice = data.subarray(0, streamMetadata.byteLength); return new Uint8Array(slice); } throw new Error("Specified physicalLevelTechnique is not supported (yet)."); } function encodeSignedInt32( values: Int32Array, streamMetadata: StreamMetadata, bitVector?: BitVector, scalingData?: GeometryScaling, ): { data: Uint32Array; runs?: number } { values = bitVector ? packNullable(values, bitVector) : new Int32Array(values); let data: Uint32Array; switch (streamMetadata.logicalLevelTechnique1) { case LogicalLevelTechnique.DELTA: if (streamMetadata.logicalLevelTechnique2 === LogicalLevelTechnique.RLE) { const encoded = encodeDeltaRleInt32(values); return { data: encoded.data, runs: encoded.runs }; } else { data = encodeZigZagDeltaInt32(values); return { data }; } case LogicalLevelTechnique.RLE: { const encoded = encodeZigZagRleInt32(values); return { data: encoded.data, runs: encoded.runs }; } case LogicalLevelTechnique.MORTON: encodeDeltaInt32(values); data = new Uint32Array(values); return { data }; case LogicalLevelTechnique.COMPONENTWISE_DELTA: if (scalingData && !bitVector) { const data = encodeComponentwiseDeltaVec2Scaled(values, scalingData.scale); return { data }; } data = encodeComponentwiseDeltaVec2(values); return { data }; case LogicalLevelTechnique.NONE: data = encodeZigZagInt32(values); return { data }; default: throw new Error( `The specified Logical level technique is not supported: ${streamMetadata.logicalLevelTechnique1}`, ); } } function encodeUnsignedInt32( values: Uint32Array, streamMetadata: StreamMetadata, bitVector?: BitVector, scalingData?: GeometryScaling, ): { data: Uint32Array; runs?: number } { values = bitVector ? packNullable(values, bitVector) : new Uint32Array(values); let data: Uint32Array; switch (streamMetadata.logicalLevelTechnique1) { case LogicalLevelTechnique.DELTA: if (streamMetadata.logicalLevelTechnique2 === LogicalLevelTechnique.RLE) { const encoded = encodeDeltaRleInt32(new Int32Array(values.buffer, values.byteOffset, values.length)); return { data: encoded.data, runs: encoded.runs }; } data = encodeZigZagDeltaInt32(new Int32Array(values.buffer, values.byteOffset, values.length)); return { data }; case LogicalLevelTechnique.RLE: { const encoded = encodeUnsignedRleInt32(values); return { data: encoded.data, runs: encoded.runs }; } case LogicalLevelTechnique.MORTON: encodeDeltaInt32(values); data = values; return { data }; case LogicalLevelTechnique.COMPONENTWISE_DELTA: if (scalingData && !bitVector) { const data = encodeComponentwiseDeltaVec2Scaled(new Int32Array(values), scalingData.scale); return { data }; } data = encodeComponentwiseDeltaVec2(new Int32Array(values)); return { data }; case LogicalLevelTechnique.NONE: data = values; return { data }; default: throw new Error( `The specified Logical level technique is not supported: ${streamMetadata.logicalLevelTechnique1}`, ); } } export function encodeFloat64(values: Float64Array, streamMetadata: StreamMetadata, isSigned: boolean): Float64Array { switch (streamMetadata.logicalLevelTechnique1) { case LogicalLevelTechnique.DELTA: encodeZigZagDeltaFloat64(values); if (streamMetadata.logicalLevelTechnique2 === LogicalLevelTechnique.RLE) { values = encodeUnsignedRleFloat64(values).data; } return values; case LogicalLevelTechnique.RLE: return encodeRleFloat64(values, isSigned); case LogicalLevelTechnique.NONE: if (isSigned) { encodeZigZagFloat64(values); } return values; default: throw new Error( `The specified Logical level technique is not supported: ${streamMetadata.logicalLevelTechnique1}`, ); } } function encodeRleFloat64(data: Float64Array, isSigned: boolean): Float64Array { return isSigned ? encodeZigZagRleFloat64(data).data : encodeUnsignedRleFloat64(data).data; } /** * Encodes BigInt64 values with zigzag encoding and varint compression */ export function encodeInt64SignedNone(values: BigInt64Array): Uint8Array { const zigzagEncoded = new BigUint64Array(Array.from(values, (val) => encodeZigZagInt64Value(val))); return encodeVarintInt64(zigzagEncoded); } /** * Encodes BigInt64 values with delta encoding, zigzag, and varint */ export function encodeInt64SignedDelta(values: BigInt64Array): Uint8Array { const deltaEncoded = new BigInt64Array(values.length); deltaEncoded[0] = values[0]; for (let i = 1; i < values.length; i++) { deltaEncoded[i] = values[i] - values[i - 1]; } const zigzagEncoded = new BigUint64Array(deltaEncoded.length); for (let i = 0; i < deltaEncoded.length; i++) { zigzagEncoded[i] = encodeZigZagInt64Value(deltaEncoded[i]); } return encodeVarintInt64(zigzagEncoded); } /** * Encodes BigInt64 values with RLE, zigzag, and varint * @param runs - Array of [runLength, value] pairs */ export function encodeInt64SignedRle(runs: Array<[number, bigint]>): Uint8Array { const runLengths: bigint[] = []; const values: bigint[] = []; for (const [runLength, value] of runs) { runLengths.push(BigInt(runLength)); values.push(encodeZigZagInt64Value(value)); } const rleValues = [...runLengths, ...values]; return encodeVarintInt64(new BigUint64Array(rleValues)); } /** * Encodes BigInt64 values with delta+RLE, zigzag, and varint * @param runs - Array of [runLength, deltaValue] pairs representing RLE-encoded delta values */ export function encodeInt64SignedDeltaRle(runs: Array<[number, bigint]>): Uint8Array { const runLengths: bigint[] = []; const values: bigint[] = []; for (const [runLength, value] of runs) { runLengths.push(BigInt(runLength)); values.push(encodeZigZagInt64Value(value)); } const rleValues = [...runLengths, ...values]; return encodeVarintInt64(new BigUint64Array(rleValues)); } /** * Encodes unsigned BigInt64 values with varint compression (no zigzag) */ export function encodeInt64UnsignedNone(values: BigInt64Array): Uint8Array { return encodeVarintInt64(new BigUint64Array(values)); } ================================================ FILE: ts/src/encoding/packNullableUtils.ts ================================================ import type { TypedArrayConstructor, TypedArrayInstance } from "../decoding/unpackNullableUtils"; import BitVector from "../vector/flat/bitVector"; export function packNullable(data: T, presentBits: BitVector | null): T { // Non-nullable case: if no mask is provided, the data is already "packed" if (!presentBits) { return data; } const size = data.length; // 1. First pass: Count how many elements are actually present // This is required to allocate the correct size for the TypedArray let packedCount = 0; for (let i = 0; i < size; i++) { if (presentBits.get(i)) { packedCount++; } } // 2. Create a new array of the same type with the reduced size const constructor = data.constructor as TypedArrayConstructor; const result = new constructor(packedCount) as T; // 3. Second pass: Fill the result array with valid values let counter = 0; for (let i = 0; i < size; i++) { if (presentBits.get(i)) { result[counter++] = data[i]; } } return result; } export function packNullableBoolean(data: Uint8Array, dataSize: number, presentBits: BitVector | null): Uint8Array { // Non-nullable case: if no mask is provided, the data is already "packed" if (!presentBits) { return data; } const inputBitVector = new BitVector(data, dataSize); // 1. Calculate how many bits are actually marked as 'present' // This determines the size of the final packed buffer. let packedCount = 0; for (let i = 0; i < dataSize; i++) { if (presentBits.get(i)) { packedCount++; } } // 2. Initialize the result BitVector with the correct compressed size const resultBuffer = new Uint8Array(Math.ceil(packedCount / 8)); const resultBitVector = new BitVector(resultBuffer, packedCount); // 3. Fill the result: only copy bits where the mask is true let targetIndex = 0; for (let i = 0; i < dataSize; i++) { if (presentBits.get(i)) { const value = inputBitVector.get(i); resultBitVector.set(targetIndex++, value); } } return resultBitVector.getBuffer(); } ================================================ FILE: ts/src/encoding/propertyEncoder.ts ================================================ import { LogicalLevelTechnique } from "../metadata/tile/logicalLevelTechnique"; import { PhysicalLevelTechnique } from "../metadata/tile/physicalLevelTechnique"; import { PhysicalStreamType } from "../metadata/tile/physicalStreamType"; import { DictionaryType } from "../metadata/tile/dictionaryType"; import type { StreamMetadata, RleEncodedStreamMetadata } from "../metadata/tile/streamMetadataDecoder"; import IntWrapper from "../decoding/intWrapper"; import { encodeBooleanRle, encodeFloatsLE, encodeDoubleLE } from "./encodingUtils"; import { encodeVarintInt32Value, encodeVarintInt32, encodeVarintInt64, encodeZigZagInt32Value, encodeZigZagInt64Value, encodeZigZagInt32, } from "./integerEncodingUtils"; /** * Encodes INT_32 values with NONE encoding (no delta, no RLE) */ export function encodeInt32NoneColumn(values: Int32Array): Uint8Array { const zigzagEncoded = encodeZigZagInt32(values); const encodedData = encodeVarintInt32(zigzagEncoded); const streamMetadata = createStreamMetadata(LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, values.length); return buildEncodedStream(streamMetadata, encodedData); } /** * Encodes INT_32 values with DELTA encoding */ export function encodeInt32DeltaColumn(values: Int32Array): Uint8Array { // Delta encode: store deltas const deltaEncoded = new Int32Array(values.length); deltaEncoded[0] = values[0]; for (let i = 1; i < values.length; i++) { deltaEncoded[i] = values[i] - values[i - 1]; } const zigzagEncoded = encodeZigZagInt32(deltaEncoded); const encodedData = encodeVarintInt32(zigzagEncoded); const streamMetadata = createStreamMetadata(LogicalLevelTechnique.DELTA, LogicalLevelTechnique.NONE, values.length); return buildEncodedStream(streamMetadata, encodedData); } /** * Encodes INT_32 values with RLE encoding * @param runs - Array of [runLength, value] pairs */ export function encodeInt32RleColumn(runs: Array<[number, number]>): Uint8Array { const runLengths: number[] = []; const values: number[] = []; let totalValues = 0; for (const [runLength, value] of runs) { runLengths.push(runLength); values.push(encodeZigZagInt32Value(value)); totalValues += runLength; } const rleValues = [...runLengths, ...values]; const encodedData = encodeVarintInt32(new Uint32Array(rleValues)); const streamMetadata = createRleMetadata( LogicalLevelTechnique.RLE, LogicalLevelTechnique.NONE, runs.length, totalValues, ); return buildEncodedStream(streamMetadata, encodedData); } /** * Encodes INT_32 values with DELTA+RLE encoding * @param runs - Array of [runLength, deltaValue] pairs, where first value is the base */ export function encodeInt32DeltaRleColumn(runs: Array<[number, number]>): Uint8Array { const runLengths: number[] = []; const values: number[] = []; let totalValues = 0; for (const [runLength, value] of runs) { runLengths.push(runLength); values.push(encodeZigZagInt32Value(value)); totalValues += runLength; } const rleValues = [...runLengths, ...values]; const encodedData = encodeVarintInt32(new Uint32Array(rleValues)); const streamMetadata = createRleMetadata( LogicalLevelTechnique.DELTA, LogicalLevelTechnique.RLE, runs.length, totalValues, ); return buildEncodedStream(streamMetadata, encodedData); } /** * Encodes nullable INT_32 values */ export function encodeInt32NullableColumn(values: (number | null)[]): Uint8Array { const nonNullValues = values.filter((v): v is number => v !== null); const zigzagEncoded = new Uint32Array(nonNullValues.map((v) => encodeZigZagInt32Value(v))); const encodedData = encodeVarintInt32(zigzagEncoded); const dataStreamMetadata = createStreamMetadata( LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, nonNullValues.length, ); const dataStream = buildEncodedStream(dataStreamMetadata, encodedData); // Nullability stream const nullabilityValues = values.map((v) => v !== null); const nullabilityEncoded = encodeBooleanRle(nullabilityValues); const nullabilityMetadata = createStreamMetadata( LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, nullabilityValues.length, ); const nullabilityStream = buildEncodedStream(nullabilityMetadata, nullabilityEncoded); return concatenateBuffers(nullabilityStream, dataStream); } /** * Encodes UINT_32 values (no zigzag encoding) */ export function encodeUint32Column(values: Uint32Array): Uint8Array { const encodedData = encodeVarintInt32(values); const streamMetadata = createStreamMetadata(LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, values.length); return buildEncodedStream(streamMetadata, encodedData); } /** * Encodes INT_64 values with NONE encoding */ export function encodeInt64NoneColumn(values: BigInt64Array): Uint8Array { const zigzagEncoded = new BigUint64Array(Array.from(values, (val) => encodeZigZagInt64Value(val))); const encodedData = encodeVarintInt64(zigzagEncoded); const streamMetadata = createStreamMetadata(LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, values.length); return buildEncodedStream(streamMetadata, encodedData); } /** * Encodes INT_64 values with DELTA encoding */ export function encodeInt64DeltaColumn(values: BigInt64Array): Uint8Array { const deltaEncoded = new BigInt64Array(values.length); deltaEncoded[0] = values[0]; for (let i = 1; i < values.length; i++) { deltaEncoded[i] = values[i] - values[i - 1]; } const zigzagEncoded = new BigUint64Array(deltaEncoded.length); for (let i = 0; i < deltaEncoded.length; i++) { zigzagEncoded[i] = encodeZigZagInt64Value(deltaEncoded[i]); } const encodedData = encodeVarintInt64(zigzagEncoded); const streamMetadata = createStreamMetadata(LogicalLevelTechnique.DELTA, LogicalLevelTechnique.NONE, values.length); return buildEncodedStream(streamMetadata, encodedData); } /** * Encodes INT_64 values with RLE encoding */ export function encodeInt64RleColumn(runs: Array<[number, bigint]>): Uint8Array { const runLengths: bigint[] = []; const values: bigint[] = []; let totalValues = 0; for (const [runLength, value] of runs) { runLengths.push(BigInt(runLength)); values.push(encodeZigZagInt64Value(value)); totalValues += runLength; } const rleValues = [...runLengths, ...values]; const encodedData = encodeVarintInt64(new BigUint64Array(rleValues)); const streamMetadata = createRleMetadata( LogicalLevelTechnique.RLE, LogicalLevelTechnique.NONE, runs.length, totalValues, ); return buildEncodedStream(streamMetadata, encodedData); } /** * Encodes INT_64 values with DELTA+RLE encoding */ export function encodeInt64DeltaRleColumn(runs: Array<[number, bigint]>): Uint8Array { const runLengths: bigint[] = []; const values: bigint[] = []; let totalValues = 0; for (const [runLength, value] of runs) { runLengths.push(BigInt(runLength)); values.push(encodeZigZagInt64Value(value)); totalValues += runLength; } const rleValues = [...runLengths, ...values]; const encodedData = encodeVarintInt64(new BigUint64Array(rleValues)); const streamMetadata = createRleMetadata( LogicalLevelTechnique.DELTA, LogicalLevelTechnique.RLE, runs.length, totalValues, ); return buildEncodedStream(streamMetadata, encodedData); } /** * Encodes nullable INT_64 values */ export function encodeInt64NullableColumn(values: (bigint | null)[]): Uint8Array { const nonNullValues = values.filter((v): v is bigint => v !== null); const zigzagEncoded = new BigUint64Array(Array.from(nonNullValues, (val) => encodeZigZagInt64Value(val))); const encodedData = encodeVarintInt64(zigzagEncoded); const dataStreamMetadata = createStreamMetadata( LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, nonNullValues.length, ); const dataStream = buildEncodedStream(dataStreamMetadata, encodedData); const nullabilityValues = values.map((v) => v !== null); const nullabilityEncoded = encodeBooleanRle(nullabilityValues); const nullabilityMetadata = createStreamMetadata( LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, nullabilityValues.length, ); const nullabilityStream = buildEncodedStream(nullabilityMetadata, nullabilityEncoded); return concatenateBuffers(nullabilityStream, dataStream); } /** * Encodes UINT_64 values (no zigzag encoding) */ export function encodeUint64Column(values: BigUint64Array): Uint8Array { const encodedData = encodeVarintInt64(values); const streamMetadata = createStreamMetadata(LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, values.length); return buildEncodedStream(streamMetadata, encodedData); } /** * Encodes nullable UINT_64 values */ export function encodeUint64NullableColumn(values: (bigint | null)[]): Uint8Array { const nonNullValues = values.filter((v): v is bigint => v !== null); const encodedData = encodeVarintInt64(new BigUint64Array(nonNullValues)); const dataStreamMetadata = createStreamMetadata( LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, nonNullValues.length, ); const dataStream = buildEncodedStream(dataStreamMetadata, encodedData); const nullabilityValues = values.map((v) => v !== null); const nullabilityEncoded = encodeBooleanRle(nullabilityValues); const nullabilityMetadata = createStreamMetadata( LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, nullabilityValues.length, ); const nullabilityStream = buildEncodedStream(nullabilityMetadata, nullabilityEncoded); return concatenateBuffers(nullabilityStream, dataStream); } /** * Encodes FLOAT values */ export function encodeFloatColumn(values: Float32Array): Uint8Array { const encodedData = encodeFloatsLE(values); const streamMetadata = createStreamMetadata(LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, values.length); return buildEncodedStream(streamMetadata, encodedData); } /** * Encodes nullable FLOAT values */ export function encodeFloatNullableColumn(values: (number | null)[]): Uint8Array { const nonNullValues = values.filter((v): v is number => v !== null); const encodedData = encodeFloatsLE(new Float32Array(nonNullValues)); const dataStreamMetadata = createStreamMetadata( LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, nonNullValues.length, ); const dataStream = buildEncodedStream(dataStreamMetadata, encodedData); const nullabilityValues = values.map((v) => v !== null); const nullabilityEncoded = encodeBooleanRle(nullabilityValues); const nullabilityMetadata = createStreamMetadata( LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, nullabilityValues.length, ); const nullabilityStream = buildEncodedStream(nullabilityMetadata, nullabilityEncoded); return concatenateBuffers(nullabilityStream, dataStream); } /** * Encodes DOUBLE values */ export function encodeDoubleColumn(values: Float64Array): Uint8Array { const encodedData = encodeDoubleLE(values); const streamMetadata = createStreamMetadata(LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, values.length); return buildEncodedStream(streamMetadata, encodedData); } /** * Encodes nullable DOUBLE values */ export function encodeDoubleNullableColumn(values: (number | null)[]): Uint8Array { const nonNullValues = values.filter((v): v is number => v !== null); const encodedData = encodeDoubleLE(new Float64Array(nonNullValues)); const dataStreamMetadata = createStreamMetadata( LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, nonNullValues.length, ); const dataStream = buildEncodedStream(dataStreamMetadata, encodedData); const nullabilityValues = values.map((v) => v !== null); const nullabilityEncoded = encodeBooleanRle(nullabilityValues); const nullabilityMetadata = createStreamMetadata( LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, nullabilityValues.length, ); const nullabilityStream = buildEncodedStream(nullabilityMetadata, nullabilityEncoded); return concatenateBuffers(nullabilityStream, dataStream); } /** * Encodes BOOLEAN values */ export function encodeBooleanColumn(values: boolean[]): Uint8Array { const encodedData = encodeBooleanRle(values); const streamMetadata = createStreamMetadata(LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, values.length); return buildEncodedStream(streamMetadata, encodedData); } /** * Encodes nullable BOOLEAN values */ export function encodeBooleanNullableColumn(values: (boolean | null)[]): Uint8Array { const nonNullValues = values.filter((v): v is boolean => v !== null); const encodedData = encodeBooleanRle(nonNullValues); const dataStreamMetadata = createStreamMetadata( LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, nonNullValues.length, ); const dataStream = buildEncodedStream(dataStreamMetadata, encodedData); const nullabilityValues = values.map((v) => v !== null); const nullabilityEncoded = encodeBooleanRle(nullabilityValues); const nullabilityMetadata = createStreamMetadata( LogicalLevelTechnique.NONE, LogicalLevelTechnique.NONE, nullabilityValues.length, ); const nullabilityStream = buildEncodedStream(nullabilityMetadata, nullabilityEncoded); return concatenateBuffers(nullabilityStream, dataStream); } function createStreamMetadata( logicalTechnique1: LogicalLevelTechnique, logicalTechnique2: LogicalLevelTechnique = LogicalLevelTechnique.NONE, numValues = 3, ): StreamMetadata { return { physicalStreamType: PhysicalStreamType.DATA, logicalStreamType: { dictionaryType: DictionaryType.NONE }, logicalLevelTechnique1: logicalTechnique1, logicalLevelTechnique2: logicalTechnique2, physicalLevelTechnique: PhysicalLevelTechnique.VARINT, numValues, byteLength: 10, decompressedCount: numValues, }; } function createRleMetadata( logicalTechnique1: LogicalLevelTechnique, logicalTechnique2: LogicalLevelTechnique, runs: number, numRleValues: number, ): RleEncodedStreamMetadata { return { physicalStreamType: PhysicalStreamType.DATA, logicalStreamType: { dictionaryType: DictionaryType.NONE }, logicalLevelTechnique1: logicalTechnique1, logicalLevelTechnique2: logicalTechnique2, physicalLevelTechnique: PhysicalLevelTechnique.VARINT, numValues: runs * 2, byteLength: 10, decompressedCount: numRleValues, runs, numRleValues, }; } function buildEncodedStream( streamMetadata: StreamMetadata | RleEncodedStreamMetadata, encodedData: Uint8Array, ): Uint8Array { const updatedMetadata = { ...streamMetadata, byteLength: encodedData.length, }; const metadataBuffer = encodeStreamMetadata(updatedMetadata); const result = new Uint8Array(metadataBuffer.length + encodedData.length); result.set(metadataBuffer, 0); result.set(encodedData, metadataBuffer.length); return result; } function encodeStreamMetadata(metadata: StreamMetadata | RleEncodedStreamMetadata): Uint8Array { const buffer = new Uint8Array(100); let writeOffset = 0; // Byte 1: Stream type const physicalTypeIndex = Object.values(PhysicalStreamType).indexOf(metadata.physicalStreamType); const lowerNibble = 0; // For DATA stream with NONE dictionary type buffer[writeOffset++] = (physicalTypeIndex << 4) | lowerNibble; // Byte 2: Encoding techniques const llt1Index = Object.values(LogicalLevelTechnique).indexOf(metadata.logicalLevelTechnique1); const llt2Index = Object.values(LogicalLevelTechnique).indexOf(metadata.logicalLevelTechnique2); const pltIndex = Object.values(PhysicalLevelTechnique).indexOf(metadata.physicalLevelTechnique); buffer[writeOffset++] = (llt1Index << 5) | (llt2Index << 2) | pltIndex; // Variable-length fields const offset = new IntWrapper(writeOffset); encodeVarintInt32Value(metadata.numValues, buffer, offset); encodeVarintInt32Value(metadata.byteLength, buffer, offset); // RLE-specific fields if (isRleMetadata(metadata)) { encodeVarintInt32Value(metadata.runs, buffer, offset); encodeVarintInt32Value(metadata.numRleValues, buffer, offset); } return buffer.slice(0, offset.get()); } function isRleMetadata(metadata: StreamMetadata | RleEncodedStreamMetadata): metadata is RleEncodedStreamMetadata { return "runs" in metadata && "numRleValues" in metadata; } function concatenateBuffers(...buffers: Uint8Array[]): Uint8Array { const totalLength = buffers.reduce((sum, buf) => sum + buf.length, 0); const result = new Uint8Array(totalLength); let offset = 0; for (const buffer of buffers) { result.set(buffer, offset); offset += buffer.length; } return result; } ================================================ FILE: ts/src/encoding/stringEncoder.ts ================================================ import { PhysicalStreamType } from "../metadata/tile/physicalStreamType"; import { DictionaryType } from "../metadata/tile/dictionaryType"; import { LengthType } from "../metadata/tile/lengthType"; import { OffsetType } from "../metadata/tile/offsetType"; import { PhysicalLevelTechnique } from "../metadata/tile/physicalLevelTechnique"; import { LogicalLevelTechnique } from "../metadata/tile/logicalLevelTechnique"; import IntWrapper from "../decoding/intWrapper"; import { encodeBooleanRle, encodeStrings, createStringLengths, concatenateBuffers } from "./encodingUtils"; import { encodeVarintInt32Value, encodeVarintInt32 } from "./integerEncodingUtils"; import type { StreamMetadata } from "../metadata/tile/streamMetadataDecoder"; import type { LogicalStreamType } from "../metadata/tile/logicalStreamType"; /** * Encodes plain strings into a complete stream with PRESENT (if needed), LENGTH, and DATA streams. * @param strings - Array of strings (can include null values) * @returns Encoded Uint8Array that can be passed to decodeString */ export function encodePlainStrings(strings: (string | null)[]): Uint8Array { const hasNull = strings.some((s) => s === null); const nonNullStrings = strings.filter((s): s is string => s !== null); const stringBytes = encodeStrings(nonNullStrings); const streams: Uint8Array[] = []; // Add PRESENT stream if nulls exist if (hasNull) { const nullabilityValues = strings.map((s) => s !== null); streams.push( createStream(PhysicalStreamType.PRESENT, encodeBooleanRle(nullabilityValues), { technique: PhysicalLevelTechnique.VARINT, count: nullabilityValues.length, }), ); } // Add LENGTH stream const lengths = createStringLengths(nonNullStrings); streams.push( createStream(PhysicalStreamType.LENGTH, encodeVarintInt32(lengths), { logical: { lengthType: LengthType.VAR_BINARY }, technique: PhysicalLevelTechnique.VARINT, count: lengths.length, }), ); // Add DATA stream streams.push( createStream(PhysicalStreamType.DATA, stringBytes, { logical: { dictionaryType: DictionaryType.NONE }, }), ); return concatenateBuffers(...streams); } /** * Encodes dictionary-compressed strings into a complete stream. * @param strings - Array of strings (can include null values) * @returns Encoded Uint8Array that can be passed to decodeString */ export function encodeDictionaryStrings(strings: (string | null)[]): Uint8Array { const hasNull = strings.some((s) => s === null); const nonNullStrings = strings.filter((s): s is string => s !== null); // Create dictionary of unique strings const uniqueStrings = Array.from(new Set(nonNullStrings)); const stringMap = new Map(uniqueStrings.map((s, i) => [s, i])); const offsets = nonNullStrings.map((s) => { const offset = stringMap.get(s); if (offset === undefined) { throw new Error(`String not found in dictionary: ${s}`); } return offset; }); const stringBytes = encodeStrings(uniqueStrings); const lengths = createStringLengths(uniqueStrings); const streams: Uint8Array[] = []; // Add PRESENT stream if nulls exist if (hasNull) { const nullabilityValues = strings.map((s) => s !== null); streams.push( createStream(PhysicalStreamType.PRESENT, encodeBooleanRle(nullabilityValues), { technique: PhysicalLevelTechnique.VARINT, count: nullabilityValues.length, }), ); } // Add OFFSET stream streams.push( createStream(PhysicalStreamType.OFFSET, encodeVarintInt32(new Uint32Array(offsets)), { logical: { offsetType: OffsetType.STRING }, technique: PhysicalLevelTechnique.VARINT, count: offsets.length, }), ); // Add LENGTH stream (for dictionary) streams.push( createStream(PhysicalStreamType.LENGTH, encodeVarintInt32(lengths), { logical: { lengthType: LengthType.DICTIONARY }, technique: PhysicalLevelTechnique.VARINT, count: lengths.length, }), ); // Add DATA stream streams.push( createStream(PhysicalStreamType.DATA, stringBytes, { logical: { dictionaryType: DictionaryType.SINGLE }, }), ); return concatenateBuffers(...streams); } function createStream( physicalType: PhysicalStreamType, data: Uint8Array, options: { logical?: LogicalStreamType; technique?: PhysicalLevelTechnique; count?: number; } = {}, ): Uint8Array { const count = options.count ?? 0; return buildEncodedStream( { physicalStreamType: physicalType, logicalStreamType: options.logical ?? {}, logicalLevelTechnique1: LogicalLevelTechnique.NONE, logicalLevelTechnique2: LogicalLevelTechnique.NONE, physicalLevelTechnique: options.technique ?? PhysicalLevelTechnique.NONE, numValues: count, byteLength: data.length, decompressedCount: count, }, data, ); } function buildEncodedStream(streamMetadata: StreamMetadata, encodedData: Uint8Array): Uint8Array { const updatedMetadata = { ...streamMetadata, byteLength: encodedData.length, }; const metadataBuffer = encodeStreamMetadata(updatedMetadata); const result = new Uint8Array(metadataBuffer.length + encodedData.length); result.set(metadataBuffer, 0); result.set(encodedData, metadataBuffer.length); return result; } function encodeStreamMetadata(metadata: StreamMetadata): Uint8Array { const buffer = new Uint8Array(100); let writeOffset = 0; // Byte 1: Stream type const physicalTypeIndex = Object.values(PhysicalStreamType).indexOf(metadata.physicalStreamType); const lowerNibble = getLogicalSubtypeValue(metadata); buffer[writeOffset++] = (physicalTypeIndex << 4) | lowerNibble; // Byte 2: Encoding techniques const llt1Index = Object.values(LogicalLevelTechnique).indexOf(metadata.logicalLevelTechnique1); const llt2Index = Object.values(LogicalLevelTechnique).indexOf(metadata.logicalLevelTechnique2); const pltIndex = Object.values(PhysicalLevelTechnique).indexOf(metadata.physicalLevelTechnique); buffer[writeOffset++] = (llt1Index << 5) | (llt2Index << 2) | pltIndex; // Variable-length fields const offset = new IntWrapper(writeOffset); encodeVarintInt32Value(metadata.numValues, buffer, offset); encodeVarintInt32Value(metadata.byteLength, buffer, offset); return buffer.slice(0, offset.get()); } function getLogicalSubtypeValue(metadata: StreamMetadata): number { const { physicalStreamType, logicalStreamType } = metadata; switch (physicalStreamType) { case PhysicalStreamType.DATA: return logicalStreamType.dictionaryType !== undefined ? Object.values(DictionaryType).indexOf(logicalStreamType.dictionaryType) : 0; case PhysicalStreamType.OFFSET: return logicalStreamType.offsetType !== undefined ? Object.values(OffsetType).indexOf(logicalStreamType.offsetType) : 0; case PhysicalStreamType.LENGTH: return logicalStreamType.lengthType !== undefined ? Object.values(LengthType).indexOf(logicalStreamType.lengthType) : 0; default: return 0; } } ================================================ FILE: ts/src/encoding/zOrderCurveEncoder.ts ================================================ export function encodeZOrderCurve(x: number, y: number, numBits: number, coordinateShift: number): number { const shiftedX = x + coordinateShift; const shiftedY = y + coordinateShift; let code = 0; for (let i = 0; i < numBits; i++) { code |= ((shiftedX & (1 << i)) << i) | ((shiftedY & (1 << i)) << (i + 1)); } return code; } ================================================ FILE: ts/src/index.ts ================================================ export { default as decodeTile } from "./mltDecoder"; export { default as FeatureTable } from "./vector/featureTable"; export { GeometryVector } from "./vector/geometry/geometryVector"; export { GpuVector } from "./vector/geometry/gpuVector"; export { default as GeometryScaling } from "./decoding/geometryScaling"; export { GEOMETRY_TYPE } from "./vector/geometry/geometryType"; export type { TileSetMetadata } from "./metadata/tileset/tilesetMetadata"; export type { Geometry } from "./vector/geometry/geometryVector"; export type { Feature } from "./vector/featureTable"; ================================================ FILE: ts/src/metadata/tile/dictionaryType.ts ================================================ export enum DictionaryType { NONE = "NONE", SINGLE = "SINGLE", SHARED = "SHARED", VERTEX = "VERTEX", MORTON = "MORTON", FSST = "FSST", } ================================================ FILE: ts/src/metadata/tile/lengthType.ts ================================================ export enum LengthType { VAR_BINARY = "VAR_BINARY", GEOMETRIES = "GEOMETRIES", PARTS = "PARTS", RINGS = "RINGS", TRIANGLES = "TRIANGLES", SYMBOL = "SYMBOL", DICTIONARY = "DICTIONARY", } ================================================ FILE: ts/src/metadata/tile/logicalLevelTechnique.ts ================================================ export enum LogicalLevelTechnique { NONE = "NONE", DELTA = "DELTA", COMPONENTWISE_DELTA = "COMPONENTWISE_DELTA", RLE = "RLE", MORTON = "MORTON", // Pseudodecimal Encoding of floats -> only for the exponent integer part an additional logical level technique is used. // Both exponent and significant parts are encoded with the same physical level technique PDE = "PDE", } ================================================ FILE: ts/src/metadata/tile/logicalStreamType.ts ================================================ import type { DictionaryType } from "./dictionaryType"; import type { OffsetType } from "./offsetType"; import type { LengthType } from "./lengthType"; export type LogicalStreamType = { readonly dictionaryType?: DictionaryType; readonly offsetType?: OffsetType; readonly lengthType?: LengthType; }; ================================================ FILE: ts/src/metadata/tile/offsetType.ts ================================================ export enum OffsetType { VERTEX = "VERTEX", INDEX = "INDEX", STRING = "STRING", KEY = "KEY", } ================================================ FILE: ts/src/metadata/tile/physicalLevelTechnique.ts ================================================ export enum PhysicalLevelTechnique { NONE = "NONE", /** * Preferred option, tends to produce the best compression ratio and decoding performance. * But currently only limited to 32 bit integer. */ FAST_PFOR = "FAST_PFOR", /** * Can produce better results in combination with a heavyweight compression scheme like Gzip. * Simple compression scheme where the decoder are easier to implement compared to FastPfor. */ VARINT = "VARINT", } ================================================ FILE: ts/src/metadata/tile/physicalStreamType.ts ================================================ export enum PhysicalStreamType { PRESENT = "PRESENT", DATA = "DATA", OFFSET = "OFFSET", LENGTH = "LENGTH", } ================================================ FILE: ts/src/metadata/tile/scalarType.ts ================================================ export enum ScalarType { BOOLEAN = 0, INT_8 = 1, UINT_8 = 2, INT_32 = 3, UINT_32 = 4, INT_64 = 5, UINT_64 = 6, FLOAT = 7, DOUBLE = 8, STRING = 9, } ================================================ FILE: ts/src/metadata/tile/streamMetadataDecoder.ts ================================================ import { LogicalLevelTechnique } from "./logicalLevelTechnique"; import { PhysicalLevelTechnique } from "./physicalLevelTechnique"; import { decodeVarintInt32 } from "../../decoding/integerDecodingUtils"; import { PhysicalStreamType } from "./physicalStreamType"; import { DictionaryType } from "./dictionaryType"; import { OffsetType } from "./offsetType"; import { LengthType } from "./lengthType"; import type { LogicalStreamType } from "./logicalStreamType"; import type IntWrapper from "../../decoding/intWrapper"; export type StreamMetadata = { readonly physicalStreamType: PhysicalStreamType; readonly logicalStreamType: LogicalStreamType; readonly logicalLevelTechnique1: LogicalLevelTechnique; readonly logicalLevelTechnique2: LogicalLevelTechnique; readonly physicalLevelTechnique: PhysicalLevelTechnique; readonly numValues: number; readonly byteLength: number; /** * Returns the number of decompressed values. * For non-RLE streams, this is the same as numValues. * For RLE streams, this is overridden to return numRleValues. */ readonly decompressedCount: number; }; export type MortonEncodedStreamMetadata = StreamMetadata & { readonly numBits: number; readonly coordinateShift: number; }; export type RleEncodedStreamMetadata = StreamMetadata & { readonly runs: number; readonly numRleValues: number; }; export function decodeStreamMetadata(tile: Uint8Array, offset: IntWrapper): StreamMetadata { const streamMetadata = decodeStreamMetadataInternal(tile, offset); if (streamMetadata.logicalLevelTechnique1 === LogicalLevelTechnique.MORTON) { return decodePartialMortonEncodedStreamMetadata(streamMetadata, tile, offset); } if ( (LogicalLevelTechnique.RLE === streamMetadata.logicalLevelTechnique1 || LogicalLevelTechnique.RLE === streamMetadata.logicalLevelTechnique2) && PhysicalLevelTechnique.NONE !== streamMetadata.physicalLevelTechnique ) { return decodePartialRleEncodedStreamMetadata(streamMetadata, tile, offset); } return streamMetadata; } function decodePartialMortonEncodedStreamMetadata( streamMetadata: StreamMetadata, tile: Uint8Array, offset: IntWrapper, ): MortonEncodedStreamMetadata { const mortonInfo = decodeVarintInt32(tile, offset, 2); return { physicalStreamType: streamMetadata.physicalStreamType, logicalStreamType: streamMetadata.logicalStreamType, logicalLevelTechnique1: streamMetadata.logicalLevelTechnique1, logicalLevelTechnique2: streamMetadata.logicalLevelTechnique2, physicalLevelTechnique: streamMetadata.physicalLevelTechnique, numValues: streamMetadata.numValues, byteLength: streamMetadata.byteLength, decompressedCount: streamMetadata.decompressedCount, numBits: mortonInfo[0], coordinateShift: mortonInfo[1], }; } function decodePartialRleEncodedStreamMetadata( streamMetadata: StreamMetadata, tile: Uint8Array, offset: IntWrapper, ): RleEncodedStreamMetadata { const rleInfo = decodeVarintInt32(tile, offset, 2); return { physicalStreamType: streamMetadata.physicalStreamType, logicalStreamType: streamMetadata.logicalStreamType, logicalLevelTechnique1: streamMetadata.logicalLevelTechnique1, logicalLevelTechnique2: streamMetadata.logicalLevelTechnique2, physicalLevelTechnique: streamMetadata.physicalLevelTechnique, numValues: streamMetadata.numValues, byteLength: streamMetadata.byteLength, decompressedCount: rleInfo[1], runs: rleInfo[0], numRleValues: rleInfo[1], }; } function decodeStreamMetadataInternal(tile: Uint8Array, offset: IntWrapper): StreamMetadata { const stream_type = tile[offset.get()]; const physicalStreamType = Object.values(PhysicalStreamType)[stream_type >> 4] as PhysicalStreamType; let logicalStreamType: LogicalStreamType | null = null; switch (physicalStreamType) { case PhysicalStreamType.DATA: logicalStreamType = { dictionaryType: Object.values(DictionaryType)[stream_type & 0xf], }; break; case PhysicalStreamType.OFFSET: logicalStreamType = { offsetType: Object.values(OffsetType)[stream_type & 0xf], }; break; case PhysicalStreamType.LENGTH: logicalStreamType = { lengthType: Object.values(LengthType)[stream_type & 0xf], }; break; } offset.increment(); const encodings_header = tile[offset.get()]; const llt1 = Object.values(LogicalLevelTechnique)[encodings_header >> 5] as LogicalLevelTechnique; const llt2 = Object.values(LogicalLevelTechnique)[(encodings_header >> 2) & 0x7] as LogicalLevelTechnique; const plt = Object.values(PhysicalLevelTechnique)[encodings_header & 0x3] as PhysicalLevelTechnique; offset.increment(); const sizeInfo = decodeVarintInt32(tile, offset, 2); const numValues = sizeInfo[0]; const byteLength = sizeInfo[1]; return { physicalStreamType, logicalStreamType, logicalLevelTechnique1: llt1, logicalLevelTechnique2: llt2, physicalLevelTechnique: plt, numValues, byteLength, decompressedCount: numValues, }; } ================================================ FILE: ts/src/metadata/tileset/embeddedTilesetMetadataDecoder.spec.ts ================================================ import { describe, expect, it } from "vitest"; import { decodeField, decodeEmbeddedTileSetMetadata } from "./embeddedTilesetMetadataDecoder"; import IntWrapper from "../../decoding/intWrapper"; import { concatenateBuffers } from "../../decoding/decodingTestUtils"; import { ComplexType, LogicalScalarType, ScalarType } from "./tilesetMetadata"; import { encodeChildCount, encodeFieldName, encodeTypeCode, scalarTypeCode, } from "../../encoding/embeddedTilesetMetadataEncoder"; const STRUCT_TYPE_CODE = 30; describe("embeddedTilesetMetadataDecoder", () => { describe("decodeField", () => { describe("scalar fields", () => { it("should decode non-nullable STRING field", () => { const buffer = concatenateBuffers( encodeTypeCode(scalarTypeCode(ScalarType.STRING, false)), encodeFieldName("street"), ); const field = decodeField(buffer, new IntWrapper(0)); expect(field.name).toBe("street"); expect(field.nullable).toBe(false); expect(field.type).toBe("scalarField"); expect(field.scalarField?.physicalType).toBe(ScalarType.STRING); }); it("should decode nullable UINT_64 field", () => { const buffer = concatenateBuffers( encodeTypeCode(scalarTypeCode(ScalarType.UINT_64, true)), encodeFieldName("population"), ); const field = decodeField(buffer, new IntWrapper(0)); expect(field.name).toBe("population"); expect(field.nullable).toBe(true); expect(field.type).toBe("scalarField"); expect(field.scalarField?.physicalType).toBe(ScalarType.UINT_64); }); it("should decode BOOLEAN field", () => { const buffer = concatenateBuffers( encodeTypeCode(scalarTypeCode(ScalarType.BOOLEAN, false)), encodeFieldName("isActive"), ); const field = decodeField(buffer, new IntWrapper(0)); expect(field.name).toBe("isActive"); expect(field.nullable).toBe(false); expect(field.type).toBe("scalarField"); expect(field.scalarField?.physicalType).toBe(ScalarType.BOOLEAN); }); it("should decode non-nullable UINT_32 field", () => { const buffer = concatenateBuffers( encodeTypeCode(scalarTypeCode(ScalarType.UINT_32, false)), encodeFieldName("count"), ); const field = decodeField(buffer, new IntWrapper(0)); expect(field.name).toBe("count"); expect(field.nullable).toBe(false); expect(field.type).toBe("scalarField"); expect(field.scalarField?.physicalType).toBe(ScalarType.UINT_32); }); it("should decode nullable FLOAT field", () => { const buffer = concatenateBuffers( encodeTypeCode(scalarTypeCode(ScalarType.FLOAT, true)), encodeFieldName("temperature"), ); const field = decodeField(buffer, new IntWrapper(0)); expect(field.name).toBe("temperature"); expect(field.nullable).toBe(true); expect(field.type).toBe("scalarField"); expect(field.scalarField?.physicalType).toBe(ScalarType.FLOAT); }); }); describe("complex fields", () => { it("should decode STRUCT field with nested children", () => { const children = [ { typeCode: scalarTypeCode(ScalarType.STRING, false), name: "street", nullable: false, physicalType: ScalarType.STRING, }, { typeCode: scalarTypeCode(ScalarType.UINT_32, true), name: "zipcode", nullable: true, physicalType: ScalarType.UINT_32, }, ]; const buffer = concatenateBuffers( encodeTypeCode(STRUCT_TYPE_CODE), encodeFieldName("address"), encodeChildCount(children.length), ...children.flatMap((c) => [encodeTypeCode(c.typeCode), encodeFieldName(c.name)]), ); const field = decodeField(buffer, new IntWrapper(0)); expect(field.name).toBe("address"); expect(field.nullable).toBe(false); expect(field.type).toBe("complexField"); expect(field.complexField?.physicalType).toBe(ComplexType.STRUCT); expect(field.complexField?.children).toHaveLength(children.length); for (let i = 0; i < children.length; i++) { const child = children[i]; expect(field.complexField?.children[i].name).toBe(child.name); expect(field.complexField?.children[i].nullable).toBe(child.nullable); expect(field.complexField?.children[i].scalarField?.physicalType).toBe(child.physicalType); } }); }); describe("deeply nested structures", () => { it("should decode 3-level nested STRUCT", () => { const leafChildren = [ { typeCode: scalarTypeCode(ScalarType.FLOAT, false), name: "lat" }, { typeCode: scalarTypeCode(ScalarType.FLOAT, false), name: "lon" }, ]; const buffer = concatenateBuffers( // Parent STRUCT "location" encodeTypeCode(STRUCT_TYPE_CODE), encodeFieldName("location"), encodeChildCount(1), // Child STRUCT "address" encodeTypeCode(STRUCT_TYPE_CODE), encodeFieldName("address"), encodeChildCount(1), // Grandchild STRUCT "coordinates" encodeTypeCode(STRUCT_TYPE_CODE), encodeFieldName("coordinates"), encodeChildCount(leafChildren.length), // Great-grandchildren ...leafChildren.flatMap((c) => [encodeTypeCode(c.typeCode), encodeFieldName(c.name)]), ); const field = decodeField(buffer, new IntWrapper(0)); expect(field.name).toBe("location"); expect(field.type).toBe("complexField"); expect(field.complexField?.physicalType).toBe(ComplexType.STRUCT); const address = field.complexField?.children[0]; expect(address?.name).toBe("address"); expect(address?.type).toBe("complexField"); const coordinates = address?.complexField?.children[0]; expect(coordinates?.name).toBe("coordinates"); expect(coordinates?.complexField?.children).toHaveLength(leafChildren.length); for (let i = 0; i < leafChildren.length; i++) { const child = leafChildren[i]; expect(coordinates?.complexField?.children[i].name).toBe(child.name); expect(coordinates?.complexField?.children[i].scalarField?.physicalType).toBe(ScalarType.FLOAT); } }); }); describe("offset tracking", () => { it("should correctly advance offset", () => { const buffer = concatenateBuffers( encodeTypeCode(scalarTypeCode(ScalarType.STRING, false)), encodeFieldName("test"), ); const offset = new IntWrapper(0); decodeField(buffer, offset); expect(offset.get()).toBe(buffer.length); }); }); describe("error handling", () => { it("should throw error for unsupported typeCode", () => { const buffer = encodeTypeCode(999); expect(() => { decodeField(buffer, new IntWrapper(0)); }).toThrow("Unsupported field type code 999. Supported: 10-29(scalars), 30(STRUCT)"); }); }); }); describe("decodeEmbeddedTileSetMetadata", () => { it("should decode tileset with STRUCT column", () => { const buffer = concatenateBuffers( encodeFieldName(""), encodeTypeCode(4096), encodeChildCount(1), encodeTypeCode(STRUCT_TYPE_CODE), encodeFieldName("props"), encodeChildCount(1), encodeTypeCode(scalarTypeCode(ScalarType.STRING, false)), encodeFieldName("name"), ); const [metadata, extent] = decodeEmbeddedTileSetMetadata(buffer, new IntWrapper(0)); expect(extent).toBe(4096); expect(metadata.featureTables[0].name).toBe(""); expect(metadata.featureTables[0].columns[0].complexType.children).toHaveLength(1); }); it("should decode logical ID metadata with implicit id column name", () => { const typeCode = 3; const buffer = concatenateBuffers( encodeFieldName("layer"), encodeTypeCode(4096), encodeChildCount(1), encodeTypeCode(typeCode), ); const [metadata] = decodeEmbeddedTileSetMetadata(buffer, new IntWrapper(0)); const idColumn = metadata.featureTables[0].columns[0]; expect(idColumn.name).toBe("id"); expect(idColumn.nullable).toBe(true); expect(idColumn.type).toBe("scalarType"); expect(idColumn.scalarType?.type).toBe("logicalType"); expect(idColumn.scalarType?.logicalType).toBe(LogicalScalarType.ID); expect(idColumn.scalarType?.longID).toBe(true); }); }); }); ================================================ FILE: ts/src/metadata/tileset/embeddedTilesetMetadataDecoder.ts ================================================ import type IntWrapper from "../../decoding/intWrapper"; import { decodeVarintInt32 } from "../../decoding/integerDecodingUtils"; import type { Column, FeatureTableSchema, Field, TileSetMetadata } from "./tilesetMetadata"; import { columnTypeHasChildren, columnTypeHasName, decodeColumnType } from "./typeMap"; const textDecoder = new TextDecoder(); const SUPPORTED_COLUMN_TYPES = "0-3(ID), 4(GEOMETRY), 10-29(scalars), 30(STRUCT)"; const SUPPORTED_FIELD_TYPES = "10-29(scalars), 30(STRUCT)"; /** * Decodes a length-prefixed UTF-8 string. * Layout: [len: varint32][bytes: len] */ function decodeString(src: Uint8Array, offset: IntWrapper): string { const length = decodeVarintInt32(src, offset, 1)[0]; if (length === 0) { return ""; } const start = offset.get(); const end = start + length; const view = src.subarray(start, end); offset.add(length); return textDecoder.decode(view); } /** * Converts a Column to a Field. * Used when decoding Field metadata which has the same format as Column. */ function columnToField(column: Column): Field { return { name: column.name, nullable: column.nullable, scalarField: column.scalarType, complexField: column.complexType, type: column.type === "scalarType" ? "scalarField" : "complexField", }; } /** * Decodes a Field used as part of complex types (STRUCT children). */ export function decodeField(src: Uint8Array, offset: IntWrapper): Field { const typeCode = decodeVarintInt32(src, offset, 1)[0] >>> 0; if (typeCode < 10 || typeCode > 30) { throw new Error(`Unsupported field type code ${typeCode}. Supported: ${SUPPORTED_FIELD_TYPES}`); } const column = decodeColumnType(typeCode); if (columnTypeHasName(typeCode)) { column.name = decodeString(src, offset); } if (columnTypeHasChildren(typeCode)) { const childCount = decodeVarintInt32(src, offset, 1)[0] >>> 0; column.complexType.children = new Array(childCount); for (let i = 0; i < childCount; i++) { column.complexType.children[i] = decodeField(src, offset); } } return columnToField(column); } /** * The typeCode encodes the column type, nullable flag, and whether it has name/children. */ function decodeColumn(src: Uint8Array, offset: IntWrapper): Column { const typeCode = decodeVarintInt32(src, offset, 1)[0] >>> 0; const column = decodeColumnType(typeCode); if (!column) { throw new Error(`Unsupported column type code ${typeCode}. Supported: ${SUPPORTED_COLUMN_TYPES}`); } if (columnTypeHasName(typeCode)) { column.name = decodeString(src, offset); } else { // ID and GEOMETRY columns have implicit names if (typeCode >= 0 && typeCode <= 3) { column.name = "id"; } else if (typeCode === 4) { column.name = "geometry"; } } if (columnTypeHasChildren(typeCode)) { // Only STRUCT (typeCode 30) has children const childCount = decodeVarintInt32(src, offset, 1)[0] >>> 0; const complexCol = column.complexType; complexCol.children = new Array(childCount); for (let i = 0; i < childCount; i++) { complexCol.children[i] = decodeField(src, offset); } } return column; } /** * Top-level decoder for embedded tileset metadata. * Reads exactly ONE FeatureTableSchema from the stream. * * @param bytes The byte array containing the metadata * @param offset The current offset in the byte array (will be advanced) */ export function decodeEmbeddedTileSetMetadata(bytes: Uint8Array, offset: IntWrapper): [TileSetMetadata, number] { const meta = {} as TileSetMetadata; meta.featureTables = []; const table = {} as FeatureTableSchema; table.name = decodeString(bytes, offset); const extent = decodeVarintInt32(bytes, offset, 1)[0] >>> 0; const columnCount = decodeVarintInt32(bytes, offset, 1)[0] >>> 0; table.columns = new Array(columnCount); for (let j = 0; j < columnCount; j++) { table.columns[j] = decodeColumn(bytes, offset); } meta.featureTables.push(table); return [meta, extent]; } ================================================ FILE: ts/src/metadata/tileset/tilesetMetadata.ts ================================================ // based on ../spec/schema/mlt_tileset_metadata.proto export const ColumnScope = { FEATURE: 0, VERTEX: 1, } as const; export const ScalarType = { BOOLEAN: 0, INT_8: 1, UINT_8: 2, INT_32: 3, UINT_32: 4, INT_64: 5, UINT_64: 6, FLOAT: 7, DOUBLE: 8, STRING: 9, } as const; export const ComplexType = { GEOMETRY: 0, STRUCT: 1, } as const; export const LogicalScalarType = { ID: 0, } as const; export const LogicalComplexType = { BINARY: 0, RANGE_MAP: 1, } as const; export interface TileSetMetadata { version?: number | null; featureTables: FeatureTableSchema[]; name?: string | null; description?: string | null; attribution?: string | null; minZoom?: number | null; maxZoom?: number | null; bounds: number[]; center: number[]; } export interface FeatureTableSchema { name?: string | null; columns: Column[]; } export interface Column { name?: string | null; nullable?: boolean | null; columnScope?: number | null; scalarType?: ScalarColumn | null; complexType?: ComplexColumn | null; type?: "scalarType" | "complexType"; } export interface ScalarColumn { longID?: boolean | null; physicalType?: number | null; logicalType?: number | null; type?: "physicalType" | "logicalType"; } export interface ComplexColumn { physicalType?: number | null; logicalType?: number | null; children: Field[]; type?: "physicalType" | "logicalType"; } export interface Field { name?: string | null; nullable?: boolean | null; scalarField?: ScalarField | null; complexField?: ComplexField | null; type?: "scalarField" | "complexField"; } export interface ScalarField { physicalType?: number | null; logicalType?: number | null; type?: "physicalType" | "logicalType"; } export interface ComplexField { physicalType?: number | null; logicalType?: number | null; children: Field[]; type?: "physicalType" | "logicalType"; } ================================================ FILE: ts/src/metadata/tileset/typeMap.spec.ts ================================================ import { describe, expect, it } from "vitest"; import { type Column, ComplexType, LogicalScalarType, ScalarType } from "./tilesetMetadata"; import { decodeColumnType, isGeometryColumn, isLogicalIdColumn } from "./typeMap"; describe("typeMap helpers", () => { it("should decode ID type codes with logical metadata and bit flags", () => { const cases = [ { typeCode: 0, nullable: false, longID: false }, { typeCode: 1, nullable: true, longID: false }, { typeCode: 2, nullable: false, longID: true }, { typeCode: 3, nullable: true, longID: true }, ]; for (const { typeCode, nullable, longID } of cases) { const column = decodeColumnType(typeCode); expect(column).toMatchObject({ nullable, type: "scalarType", scalarType: { type: "logicalType", logicalType: LogicalScalarType.ID, longID, }, }); expect(isLogicalIdColumn(column)).toBe(true); } }); it("should return false for physical scalar columns even when column is named id", () => { const physicalIdNamedColumn = { name: "id", type: "scalarType", scalarType: { type: "physicalType", physicalType: ScalarType.UINT_32, }, } as Column; expect(isLogicalIdColumn(physicalIdNamedColumn)).toBe(false); }); it("should return true for logical ID columns regardless of column name", () => { const logicalIdAnyNameColumn = { name: "my_custom_id", type: "scalarType", scalarType: { type: "logicalType", logicalType: LogicalScalarType.ID, longID: true, }, } as Column; expect(isLogicalIdColumn(logicalIdAnyNameColumn)).toBe(true); }); it("should return false for STRUCT columns even when column is named geometry", () => { const structNamedGeometryColumn = { name: "geometry", type: "complexType", complexType: { type: "physicalType", physicalType: ComplexType.STRUCT, children: [], }, } as Column; expect(isGeometryColumn(structNamedGeometryColumn)).toBe(false); }); it("should return true for GEOMETRY columns regardless of column name", () => { const geometryAnyNameColumn = { name: "geom", type: "complexType", complexType: { type: "physicalType", physicalType: ComplexType.GEOMETRY, children: [], }, } as Column; expect(isGeometryColumn(geometryAnyNameColumn)).toBe(true); }); }); ================================================ FILE: ts/src/metadata/tileset/typeMap.ts ================================================ import { type Column, ColumnScope, type ComplexColumn, ComplexType, LogicalScalarType, type ScalarColumn, ScalarType, } from "./tilesetMetadata"; /** * The type code is a single varint32 that encodes: * - Physical or logical type * - Nullable flag * - Whether the column has a name (typeCode >= 10) * - Whether the column has children (typeCode == 30 for STRUCT) * - For ID types: whether it uses long (64-bit) IDs */ /** * Decodes a type code into a Column structure. * * ID type codes (0..3): * - Bit 0: nullable * - Bit 1: longID (0/1 -> uint32 IDs, 2/3 -> uint64 IDs) * * ID columns are kept as logical types so they remain distinguishable * from feature properties that may also be named "id". */ export function decodeColumnType(typeCode: number): Column | null { switch (typeCode) { case 0: case 1: case 2: case 3: { const column = {} as Column; column.nullable = (typeCode & 1) !== 0; column.columnScope = ColumnScope.FEATURE; const scalarCol = {} as ScalarColumn; scalarCol.type = "logicalType"; scalarCol.logicalType = LogicalScalarType.ID; scalarCol.longID = (typeCode & 2) !== 0; column.scalarType = scalarCol; column.type = "scalarType"; return column; } case 4: { // GEOMETRY (non-nullable, no children) const column = {} as Column; column.nullable = false; column.columnScope = ColumnScope.FEATURE; const complexCol = {} as ComplexColumn; complexCol.type = "physicalType"; complexCol.physicalType = ComplexType.GEOMETRY; column.type = "complexType"; column.complexType = complexCol; return column; } case 30: { // STRUCT (non-nullable with children) const column = {} as Column; column.nullable = false; column.columnScope = ColumnScope.FEATURE; const complexCol = {} as ComplexColumn; complexCol.type = "physicalType"; complexCol.physicalType = ComplexType.STRUCT; column.type = "complexType"; column.complexType = complexCol; return column; } default: return mapScalarType(typeCode); } } /** * Returns true if this type code requires a name to be stored. * ID (0-3) and GEOMETRY (4) columns have implicit names. * All other types (>= 10) require explicit names. */ export function columnTypeHasName(typeCode: number): boolean { return typeCode >= 10; } /** * Returns true if this type code has child fields. * Only STRUCT (typeCode 30) has children. */ export function columnTypeHasChildren(typeCode: number): boolean { return typeCode === 30; } /** * Determines if a stream count needs to be read for this column. * Mirrors the logic in cpp/include/mlt/metadata/type_map.hpp lines 85-122 */ export function hasStreamCount(column: Column): boolean { if (column.type === "scalarType") { const scalarCol = column.scalarType; if (scalarCol.type === "physicalType") { const physicalType = scalarCol.physicalType; switch (physicalType) { case ScalarType.BOOLEAN: case ScalarType.INT_8: case ScalarType.UINT_8: case ScalarType.INT_32: case ScalarType.UINT_32: case ScalarType.INT_64: case ScalarType.UINT_64: case ScalarType.FLOAT: case ScalarType.DOUBLE: return false; case ScalarType.STRING: return true; default: return false; } } if (scalarCol.type === "logicalType") { return false; } } else if (column.type === "complexType") { const complexCol = column.complexType; if (complexCol.type === "physicalType") { const physicalType = complexCol.physicalType; switch (physicalType) { case ComplexType.GEOMETRY: case ComplexType.STRUCT: return true; default: return false; } } } console.warn("Unexpected column type in hasStreamCount", column); return false; } export function isLogicalIdColumn(column: Column): boolean { return ( column.type === "scalarType" && column.scalarType?.type === "logicalType" && column.scalarType.logicalType === LogicalScalarType.ID ); } export function isGeometryColumn(column: Column): boolean { return ( column.type === "complexType" && column.complexType?.type === "physicalType" && column.complexType.physicalType === ComplexType.GEOMETRY ); } /** * Maps a scalar type code to a Column with ScalarType. * Type codes 10-29 encode scalar types with nullable flag. * Even codes are non-nullable, odd codes are nullable. */ function mapScalarType(typeCode: number): Column | null { let scalarType: number | null; switch (typeCode) { case 10: case 11: scalarType = ScalarType.BOOLEAN; break; case 12: case 13: scalarType = ScalarType.INT_8; break; case 14: case 15: scalarType = ScalarType.UINT_8; break; case 16: case 17: scalarType = ScalarType.INT_32; break; case 18: case 19: scalarType = ScalarType.UINT_32; break; case 20: case 21: scalarType = ScalarType.INT_64; break; case 22: case 23: scalarType = ScalarType.UINT_64; break; case 24: case 25: scalarType = ScalarType.FLOAT; break; case 26: case 27: scalarType = ScalarType.DOUBLE; break; case 28: case 29: scalarType = ScalarType.STRING; break; default: return null; } const column = {} as Column; column.nullable = (typeCode & 1) !== 0; column.columnScope = ColumnScope.FEATURE; const scalarCol = {} as ScalarColumn; scalarCol.type = "physicalType"; scalarCol.physicalType = scalarType; column.type = "scalarType"; column.scalarType = scalarCol; return column; } ================================================ FILE: ts/src/mltDecoder.spec.ts ================================================ import assert from "node:assert/strict"; import { describe, it } from "vitest"; import { readdirSync, readFileSync } from "node:fs"; import { parse, join } from "node:path"; import { VectorTile, type VectorTileFeature } from "@mapbox/vector-tile"; import Pbf from "pbf"; import { type FeatureTable, type Feature, decodeTile } from "."; import path from "node:path"; import fs from "node:fs"; const ITERATOR_TILE = path.resolve(__dirname, "../../test/expected/tag0x01/simple/multiline-boolean.mlt"); describe("MLT Decoder - MVT comparison for SIMPLE tiles", () => { const simpleMltTileDir = "../test/expected/tag0x01/simple"; const simpleMvtTileDir = "../test/fixtures/simple"; testTiles(simpleMltTileDir, simpleMvtTileDir); }); describe("MLT Decoder - MVT comparison for Amazon tiles", () => { const amazonMltTileDir = "../test/expected/tag0x01/amazon"; const amazonMvtTileDir = "../test/fixtures/amazon"; testTiles(amazonMltTileDir, amazonMvtTileDir); }); describe("MLT Decoder - MVT comparison for OMT tiles", () => { const omtMltTileDir = "../test/expected/tag0x01/omt"; const omtMvtTileDir = "../test/fixtures/omt"; testTiles(omtMltTileDir, omtMvtTileDir); }, 150000); describe("MLT Decoder - MVT comparison for Bing tiles", () => { const bingMltTileDir = "../test/expected/tag0x01/bing"; const bingMvtTileDir = "../test/fixtures/bing"; testTiles(bingMltTileDir, bingMvtTileDir); }, 150000); describe("FeatureTable", () => { it("should iterate through features correctly", () => { const bytes = new Uint8Array(fs.readFileSync(ITERATOR_TILE)); const featureTables = decodeTile(bytes); const table = featureTables[0]; assert.equal(table.name, "layer"); assert.equal(table.extent, 4096); let featureCount = 0; for (const feature of table.getFeatures()) { assert.ok(feature.geometry); assert.ok(Array.isArray(feature.geometry.coordinates)); assert.ok(feature.geometry.coordinates.length > 0); assert.equal(typeof feature.geometry.type, "number"); featureCount++; } assert.equal(featureCount, table.numFeatures); }); }); function testTiles(mltSearchDir: string, mvtSearchDir: string) { const mltFileNames = readdirSync(mltSearchDir) .filter((file) => parse(file).ext === ".mlt") .map((file) => parse(file).name); for (const fileName of mltFileNames) { it(`should compare ${fileName} tile`, () => { const mltFileName = `${fileName}.mlt`; const mltPath = join(mltSearchDir, mltFileName); const mvtPath = join(mvtSearchDir, `${fileName}.mvt`); const encodedMvt = readFileSync(mvtPath); const encodedMlt = readFileSync(mltPath); const buf = new Pbf(encodedMvt); const decodedMvt = new VectorTile(buf); const decodedMlt = decodeTile(encodedMlt, undefined, true); comparePlainGeometryEncodedTile(decodedMlt, decodedMvt); }); } } function removeEmptyStrings(mvtProperties: Record) { for (const key of Object.keys(mvtProperties)) { const value = mvtProperties[key]; if (typeof value === "string" && !value.length) { delete mvtProperties[key]; } } } function comparePlainGeometryEncodedTile(mlt: FeatureTable[], mvt: VectorTile) { for (const featureTable of mlt) { const layer = mvt.layers[featureTable.name]; // Use getFeatures() instead of iterator (like C++ and Java implementations) const mltFeatures = featureTable.getFeatures(); assert.equal(mltFeatures.length, layer.length); for (let j = 0; j < layer.length; j++) { const mvtFeature = layer.feature(j); const mltFeature = mltFeatures[j]; compareId(mltFeature, mvtFeature, true); const mltGeometry = mltFeature.geometry?.coordinates; const mvtGeometry = mvtFeature.loadGeometry(); assert.deepEqual(mltGeometry, mvtGeometry); const mltProperties = mltFeature.properties; const mvtProperties = mvtFeature.properties; transformPropertyNames(mltProperties); transformPropertyNames(mvtProperties); convertBigIntPropertyValues(mltProperties); //TODO: fix -> since a change in the java converter shared dictionary encoding empty strings are not //encoded anymore removeEmptyStrings(mvtProperties); removeEmptyStrings(mltProperties); assert.deepEqual(mltProperties, mvtProperties); } } } function compareId(mltFeature: Feature, mvtFeature: VectorTileFeature, idWithinMaxSafeInteger: boolean) { if (!mvtFeature.id) { /* Java MVT library in the MVT converter decodes zero for undefined ids */ assert.ok(mltFeature.id === 0 || mltFeature.id === null || mltFeature.id === 0n); } else { const mltFeatureId = mltFeature.id; /* For const and sequence vectors the decoder can return bigint compared to the vector-tile-js library */ const actualId = idWithinMaxSafeInteger && typeof mltFeatureId !== "bigint" ? mltFeatureId : Number(mltFeatureId); /* * The id check can fail for two known reasons: * - The java-vector-tile library used in the Java converter returns negative integers for the * unoptimized tileset in some tiles * - The vector-tile-js library is using number types for the id so there can only be stored * values up to 53 bits without loss of precision **/ if (mltFeatureId < 0 || mltFeatureId > Number.MAX_SAFE_INTEGER) { /* Expected to fail in some/most cases */ try { assert.equal(actualId, mvtFeature.id); } catch (_e) { //console.info("id mismatch", featureTableName, mltFeatureId, mvtFeature.id); } return; } if (!Number.isSafeInteger(mvtFeature.id)) { return; } assert.equal(actualId, mvtFeature.id); } } /* Change bigint to number for comparison with MVT */ function convertBigIntPropertyValues(mltProperties: Record) { for (const key of Object.keys(mltProperties)) { if (typeof mltProperties[key] === "bigint") { mltProperties[key] = Number(mltProperties[key]); } } } function transformPropertyNames(properties: Record) { const propertyNames = Object.keys(properties); for (let k = 0; k < propertyNames.length; k++) { const key = propertyNames[k]; let newKey = key; /* rename the property names which are separated with : in mlt to match _ in omt mvts */ if (key.startsWith("name") && key.includes(":")) { newKey = (key as any).replaceAll(":", "_"); properties[newKey] = properties[key]; delete properties[key]; } /* Currently id is not supported as a property name in a FeatureTable, * so this quick workaround is implemented */ if (newKey === "_id") { properties.id = properties[newKey]; delete properties[newKey]; } } } ================================================ FILE: ts/src/mltDecoder.ts ================================================ import FeatureTable from "./vector/featureTable"; import { type Column, LogicalScalarType, ScalarType } from "./metadata/tileset/tilesetMetadata"; import IntWrapper from "./decoding/intWrapper"; import { decodeStreamMetadata, type RleEncodedStreamMetadata } from "./metadata/tile/streamMetadataDecoder"; import { VectorType } from "./vector/vectorType"; import { Int32FlatVector } from "./vector/flat/int32FlatVector"; import BitVector from "./vector/flat/bitVector"; import { decodeUnsignedConstInt32Stream, decodeUnsignedConstInt64Stream, decodeUnsignedInt64AsFloat64Stream, decodeUnsignedInt32Stream, decodeUnsignedInt64Stream, decodeSequenceInt32Stream, decodeSequenceInt64Stream, getVectorType, } from "./decoding/integerStreamDecoder"; import { Int32SequenceVector } from "./vector/sequence/int32SequenceVector"; import { Int64FlatVector } from "./vector/flat/int64FlatVector"; import { Int64SequenceVector } from "./vector/sequence/int64SequenceVector"; import type { IdVector } from "./vector/idVector"; import { decodeVarintInt32 } from "./decoding/integerDecodingUtils"; import { decodeGeometryColumn } from "./decoding/geometryDecoder"; import { decodePropertyColumn } from "./decoding/propertyDecoder"; import { Int32ConstVector } from "./vector/constant/int32ConstVector"; import { Int64ConstVector } from "./vector/constant/int64ConstVector"; import type GeometryScaling from "./decoding/geometryScaling"; import { decodeBooleanRle } from "./decoding/decodingUtils"; import { DoubleFlatVector } from "./vector/flat/doubleFlatVector"; import { decodeEmbeddedTileSetMetadata } from "./metadata/tileset/embeddedTilesetMetadataDecoder"; import { hasStreamCount, isGeometryColumn, isLogicalIdColumn } from "./metadata/tileset/typeMap"; import type { StreamMetadata } from "./metadata/tile/streamMetadataDecoder"; import type { GeometryVector } from "./vector/geometry/geometryVector"; import type Vector from "./vector/vector"; import type { GpuVector } from "./vector/geometry/gpuVector"; /** * Decodes a tile with embedded metadata (Tag 0x01 format). * This is the primary decoder function for MLT tiles. * * @param tile The tile data to decode (will be decompressed if gzip-compressed) * @param geometryScaling Optional geometry scaling parameters * @param idWithinMaxSafeInteger If true, limits ID values to JavaScript safe integer range (53 bits) */ export default function decodeTile( tile: Uint8Array, geometryScaling?: GeometryScaling, idWithinMaxSafeInteger = true, ): FeatureTable[] { const offset = new IntWrapper(0); const featureTables: FeatureTable[] = []; while (offset.get() < tile.length) { const blockLength = decodeVarintInt32(tile, offset, 1)[0] >>> 0; const blockStart = offset.get(); const blockEnd = blockStart + blockLength; if (blockEnd > tile.length) { throw new Error(`Block overruns tile: ${blockEnd} > ${tile.length}`); } const tag = decodeVarintInt32(tile, offset, 1)[0] >>> 0; if (tag !== 1) { // Skip unknown block types offset.set(blockEnd); continue; } const [metadata, extent] = decodeEmbeddedTileSetMetadata(tile, offset); const featureTableMetadata = metadata.featureTables[0]; let idVector: IdVector | null = null; let geometryVector: GeometryVector | GpuVector | null = null; const propertyVectors: Vector[] = []; let numFeatures = 0; for (const columnMetadata of featureTableMetadata.columns) { const columnName = columnMetadata.name; if (isLogicalIdColumn(columnMetadata)) { let nullabilityBuffer = null; // Check column metadata nullable flag, not numStreams (ID columns don't have stream count) if (columnMetadata.nullable) { const presentStreamMetadata = decodeStreamMetadata(tile, offset); const streamDataStart = offset.get(); const values = decodeBooleanRle( tile, presentStreamMetadata.numValues, presentStreamMetadata.byteLength, offset, ); offset.set(streamDataStart + presentStreamMetadata.byteLength); nullabilityBuffer = new BitVector(values, presentStreamMetadata.numValues); } const idDataStreamMetadata = decodeStreamMetadata(tile, offset); // decompressedCount is the count WITHOUT nulls, but we may have nulls numFeatures = nullabilityBuffer ? nullabilityBuffer.size() : idDataStreamMetadata.decompressedCount; idVector = decodeIdColumn( tile, columnMetadata, offset, columnName, idDataStreamMetadata, nullabilityBuffer ?? numFeatures, idWithinMaxSafeInteger, ); } else if (isGeometryColumn(columnMetadata)) { const numStreams = decodeVarintInt32(tile, offset, 1)[0]; // If no ID column, get numFeatures from geometry type stream metadata if (numFeatures === 0) { const savedOffset = offset.get(); const geometryTypeMetadata = decodeStreamMetadata(tile, offset); numFeatures = geometryTypeMetadata.decompressedCount; offset.set(savedOffset); // Reset to re-read in decodeGeometryColumn } if (geometryScaling) { geometryScaling.scale = geometryScaling.extent / extent; } geometryVector = decodeGeometryColumn(tile, numStreams, offset, numFeatures, geometryScaling); } else { const columnHasStreamCount = hasStreamCount(columnMetadata); const numStreams = columnHasStreamCount ? decodeVarintInt32(tile, offset, 1)[0] : 1; if (numStreams === 0) { continue; } const propertyVector = decodePropertyColumn( tile, offset, columnMetadata, numStreams, numFeatures, undefined, ); if (propertyVector) { if (Array.isArray(propertyVector)) { for (const property of propertyVector) { propertyVectors.push(property); } } else { propertyVectors.push(propertyVector); } } } } const featureTable = new FeatureTable( featureTableMetadata.name, geometryVector, idVector, propertyVectors, extent, ); featureTables.push(featureTable); offset.set(blockEnd); } return featureTables; } function decodeIdColumn( tile: Uint8Array, columnMetadata: Column, offset: IntWrapper, columnName: string, idDataStreamMetadata: StreamMetadata, sizeOrNullabilityBuffer: number | BitVector, idWithinMaxSafeInteger = false, ): IdVector { const scalarTypeMetadata = columnMetadata.scalarType; if ( !scalarTypeMetadata || scalarTypeMetadata.type !== "logicalType" || scalarTypeMetadata.logicalType !== LogicalScalarType.ID ) { throw new Error(`ID column must be a logical ID scalar type: ${columnName}`); } const idDataType = scalarTypeMetadata.longID ? ScalarType.UINT_64 : ScalarType.UINT_32; const nullabilityBuffer = typeof sizeOrNullabilityBuffer === "number" ? undefined : sizeOrNullabilityBuffer; const vectorType = getVectorType( idDataStreamMetadata, sizeOrNullabilityBuffer, tile, offset, idDataType === ScalarType.UINT_64 ? "int64" : "int32", ); if (idDataType === ScalarType.UINT_32) { switch (vectorType) { case VectorType.FLAT: { const id = decodeUnsignedInt32Stream(tile, offset, idDataStreamMetadata, undefined, nullabilityBuffer); return new Int32FlatVector(columnName, id, sizeOrNullabilityBuffer); } case VectorType.SEQUENCE: { const id = decodeSequenceInt32Stream(tile, offset, idDataStreamMetadata); return new Int32SequenceVector( columnName, id[0], id[1], (idDataStreamMetadata as RleEncodedStreamMetadata).numRleValues, ); } case VectorType.CONST: { const id = decodeUnsignedConstInt32Stream(tile, offset, idDataStreamMetadata); return new Int32ConstVector(columnName, id, sizeOrNullabilityBuffer, false); } } } switch (vectorType) { case VectorType.FLAT: { if (idWithinMaxSafeInteger) { const id = decodeUnsignedInt64AsFloat64Stream(tile, offset, idDataStreamMetadata); return new DoubleFlatVector(columnName, id, sizeOrNullabilityBuffer); } const id = decodeUnsignedInt64Stream(tile, offset, idDataStreamMetadata, nullabilityBuffer); return new Int64FlatVector(columnName, id, sizeOrNullabilityBuffer); } case VectorType.SEQUENCE: { const id = decodeSequenceInt64Stream(tile, offset, idDataStreamMetadata); return new Int64SequenceVector( columnName, id[0], id[1], (idDataStreamMetadata as RleEncodedStreamMetadata).numRleValues, ); } case VectorType.CONST: { const id = decodeUnsignedConstInt64Stream(tile, offset, idDataStreamMetadata); return new Int64ConstVector(columnName, id, sizeOrNullabilityBuffer, false); } } throw new Error("Vector type not supported for id column."); } ================================================ FILE: ts/src/mltMetadata.ts ================================================ export enum ColumnDataType { STRING = 0, FLOAT = 1, DOUBLE = 2, INT_64 = 3, UINT_64 = 4, BOOLEAN = 5, GEOMETRY = 6, GEOMETRY_M = 7, GEOMETRY_Z = 8, GEOMETRY_ZM = 9, } export enum ColumnEncoding { /* * String -> no dictionary coding * Geometry -> standard unsorted encoding * */ PLAIN = 0, VARINT = 1, DELTA_VARINT = 2, RLE = 3, BOOLEAN_RLE = 4, BYTE_RLE = 5, DICTIONARY = 6, LOCALIZED_DICTIONARY = 7, ORDERED_GEOMETRY_ENCODING = 8, INDEXED_COORDINATE_ENCODING = 9, } export interface ColumnMetadata { columnName: string; columnType: ColumnDataType; columnEncoding: ColumnEncoding; streams: Map; } export interface StreamMetadata { numValues: number; byteLength: number; } export interface LayerMetadata { name: string; numColumns: number; numFeatures: number; columnMetadata: ColumnMetadata[]; } ================================================ FILE: ts/src/synthetic.spec.ts ================================================ import { readFile } from "node:fs/promises"; import { describe, expect, it } from "vitest"; import { classifyRings } from "@maplibre/maplibre-gl-style-spec"; import { GEOMETRY_TYPE } from "./vector/geometry/geometryType"; import { compareWithTolerance, getTestCases, writeActualOutput } from "../../test/synthetic/synthetic-test-utils"; import decodeTile from "./mltDecoder"; import type { Geometry } from "./vector/geometry/geometryVector"; import type FeatureTable from "./vector/featureTable"; const EARCUT_MAX_RINGS = 500; const UNIMPLEMENTED_SYNTHETICS: string[] = [ "0x01/multipoint_morton", "0x01/poly_morton_hole_morton", "0x01/poly_multi_morton_hole_morton", "0x01/poly_multi_morton_ring_morton", "0x01/poly_multi_morton_ring_no_morton", ]; describe("MLT Decoder - Synthetic tests", () => { expect.addEqualityTesters([compareWithTolerance]); const testCases = getTestCases(UNIMPLEMENTED_SYNTHETICS); for (const { name, content, fileName } of testCases.active) { it(name, async () => { const actual = await decodeMLT(fileName); writeActualOutput(fileName, actual); expect(actual).toEqual(content); }); } for (const skippedTest of testCases.skipped) { it.skip(skippedTest, () => { // Test is skipped since it is not supported yet }); } }); async function decodeMLT(mltFilePath: string) { const mltBuffer = await readFile(mltFilePath); const featureTables = decodeTile(mltBuffer, undefined, false); return featureTablesToFeatureCollection(featureTables) as unknown as Record; } function featureTablesToFeatureCollection(featureTables: FeatureTable[]): GeoJSON.FeatureCollection { const features: GeoJSON.Feature[] = []; for (const table of featureTables) { for (const feature of table.getFeatures()) { const geojsonFeature: GeoJSON.Feature = { type: "Feature", geometry: getGeometry(feature.geometry), properties: { _layer: table.name, _extent: table.extent, ...Object.fromEntries(Object.entries(feature.properties).map(([k, v]) => [k, safeNumber(v)])), }, }; const safeId = safeNumber(feature.id); if (safeId !== null && safeId !== undefined) { geojsonFeature.id = safeId; } features.push(geojsonFeature); } } return { type: "FeatureCollection", features }; } function safeNumber(val: bigint | T): T | number { return typeof val === "bigint" ? Number(val) : val; } function getGeometry(geometry: Geometry): GeoJSON.Geometry { const coords = geometry.coordinates.map((ring) => ring.map((p) => [p.x, p.y])); switch (geometry.type) { case GEOMETRY_TYPE.POINT: return { type: "Point", coordinates: coords[0][0] }; case GEOMETRY_TYPE.LINESTRING: return { type: "LineString", coordinates: coords[0] }; case GEOMETRY_TYPE.POLYGON: return { type: "Polygon", coordinates: coords }; case GEOMETRY_TYPE.MULTIPOINT: return { type: "MultiPoint", coordinates: coords.map((r) => r[0]) }; case GEOMETRY_TYPE.MULTILINESTRING: return { type: "MultiLineString", coordinates: coords }; case GEOMETRY_TYPE.MULTIPOLYGON: { const polygons = classifyRings(geometry.coordinates, EARCUT_MAX_RINGS); return { type: "MultiPolygon", coordinates: polygons.map((polygon) => polygon.map((ring) => ring.map((p) => [p.x, p.y]))), }; } default: throw new Error(`Unsupported geometry type: ${geometry.type}`); } } ================================================ FILE: ts/src/vector/constant/int32ConstVector.ts ================================================ import type BitVector from "../flat/bitVector"; import Vector from "../vector"; export class Int32ConstVector extends Vector { public constructor(name: string, value: number, sizeOrNullabilityBuffer: number | BitVector, isSigned: boolean) { super(name, isSigned ? Int32Array.of(value) : Uint32Array.of(value), sizeOrNullabilityBuffer); } protected getValueFromBuffer(_index: number): number { return this.dataBuffer[0]; } } ================================================ FILE: ts/src/vector/constant/int64ConstVector.ts ================================================ import type BitVector from "../flat/bitVector"; import Vector from "../vector"; export class Int64ConstVector extends Vector { public constructor(name: string, value: bigint, sizeOrNullabilityBuffer: number | BitVector, isSigned: boolean) { super(name, isSigned ? BigInt64Array.of(value) : BigUint64Array.of(value), sizeOrNullabilityBuffer); } protected getValueFromBuffer(_index: number): bigint { return this.dataBuffer[0]; } } ================================================ FILE: ts/src/vector/dictionary/stringDictionaryVector.ts ================================================ import { VariableSizeVector } from "../variableSizeVector"; import type BitVector from "../flat/bitVector"; import { decodeString } from "../../decoding/decodingUtils"; export class StringDictionaryVector extends VariableSizeVector { constructor( name: string, private readonly indexBuffer: Uint32Array, offsetBuffer: Uint32Array, dictionaryBuffer: Uint8Array, nullabilityBuffer?: BitVector, ) { super(name, offsetBuffer, dictionaryBuffer, nullabilityBuffer ?? indexBuffer.length); this.indexBuffer = indexBuffer; } protected getValueFromBuffer(index: number): string { const offset = this.indexBuffer[index]; const start = this.offsetBuffer[offset]; const end = this.offsetBuffer[offset + 1]; return decodeString(this.dataBuffer, start, end); } } ================================================ FILE: ts/src/vector/featureTable.ts ================================================ import type { Geometry, GeometryVector } from "./geometry/geometryVector"; import type Vector from "./vector"; import type { IdVector } from "./idVector"; import { Int32FlatVector } from "./flat/int32FlatVector"; import { DoubleFlatVector } from "./flat/doubleFlatVector"; import { Int32SequenceVector } from "./sequence/int32SequenceVector"; import { Int32ConstVector } from "./constant/int32ConstVector"; import type { GpuVector } from "./geometry/gpuVector"; export interface Feature { id: number | bigint; geometry: Geometry; properties: { [key: string]: unknown }; } export default class FeatureTable { private propertyVectorsMap: Map; constructor( private readonly _name: string, private readonly _geometryVector: GeometryVector | GpuVector, private readonly _idVector?: IdVector, private readonly _propertyVectors?: Vector[], private readonly _extent = 4096, ) {} get name(): string { return this._name; } get idVector(): IdVector { return this._idVector; } get geometryVector(): GeometryVector | GpuVector { return this._geometryVector; } get propertyVectors(): Vector[] { return this._propertyVectors; } getPropertyVector(name: string): Vector { if (!this.propertyVectorsMap) { this.propertyVectorsMap = new Map(this._propertyVectors.map((vector) => [vector.name, vector])); } return this.propertyVectorsMap.get(name); } get numFeatures(): number { return this.geometryVector.numGeometries; } get extent(): number { return this._extent; } /** * Returns all features as an array */ getFeatures(): Feature[] { const features: Feature[] = []; const geometries = this.geometryVector.getGeometries(); for (let i = 0; i < this.numFeatures; i++) { let id; if (this.idVector) { const idValue = this.idVector.getValue(i); id = this.containsMaxSafeIntegerValues(this.idVector) && idValue !== null ? Number(idValue) : idValue; } const geometry = { coordinates: geometries[i], type: this.geometryVector.geometryType(i), }; const properties: { [key: string]: unknown } = {}; for (const propertyColumn of this.propertyVectors) { if (!propertyColumn) continue; const columnName = propertyColumn.name; const propertyValue = propertyColumn.getValue(i); if (propertyValue !== null) { properties[columnName] = propertyValue; } } features.push({ id, geometry, properties }); } return features; } private containsMaxSafeIntegerValues(idVector: IdVector) { return ( idVector instanceof Int32FlatVector || idVector instanceof Int32ConstVector || idVector instanceof Int32SequenceVector || idVector instanceof DoubleFlatVector ); } } ================================================ FILE: ts/src/vector/filter/flatSelectionVector.spec.ts ================================================ import { describe, it, expect } from "vitest"; import { FlatSelectionVector } from "./flatSelectionVector"; describe("flatSelectionVector", () => { describe("Basic functionality", () => { it("Should store and retrieve indices", () => { const fsVector = new FlatSelectionVector([0, 1, 999999999999, -28, 36]); expect(fsVector.getIndex(0)).toBe(0); expect(fsVector.getIndex(2)).toBe(999999999999); expect(fsVector.getIndex(3)).toBe(-28); fsVector.setIndex(2, -48); expect(fsVector.getIndex(2)).toBe(-48); }); it("Should throw RangeError for out of bounds access", () => { const fsVector = new FlatSelectionVector([0, 1, 2]); expect(() => fsVector.getIndex(10)).toThrowError("Index out of bounds"); expect(() => fsVector.getIndex(-1)).toThrowError("Index out of bounds"); expect(() => fsVector.setIndex(-1, 0)).toThrowError("Index out of bounds"); expect(() => fsVector.setIndex(10, 0)).toThrowError("Index out of bounds"); }); }); describe("Array wrapper behavior", () => { it("Should return reference to underlying array", () => { const vector = [0, 1, 2, 3, 4]; const fsVector = new FlatSelectionVector(vector); expect(fsVector.selectionValues()).toBe(vector); }); it("Should use array length as default limit and capacity", () => { const fsVector = new FlatSelectionVector([1, 2, 3, 4, 5]); expect(fsVector.limit).toBe(5); expect(fsVector.capacity).toBe(5); }); it("Should allow custom limit independent of array length", () => { const fsVector = new FlatSelectionVector([1, 2, 3, 4, 5], 3); expect(fsVector.limit).toBe(3); expect(fsVector.capacity).toBe(5); }); }); describe("set Limit Tests", () => { it("should set Limit", () => { const fsVector = new FlatSelectionVector([1, 2, 3, 4, 5], 3); fsVector.setLimit(2); expect(fsVector.limit).toBe(2); }); it("should throw out of bounds error", () => { const fsVector = new FlatSelectionVector([1, 2, 3, 4, 5], 3); expect(() => fsVector.setLimit(-10)).toThrowError("Limit out of bounds"); expect(() => fsVector.setLimit(10)).toThrowError("Limit out of bounds"); }); }); }); ================================================ FILE: ts/src/vector/filter/flatSelectionVector.ts ================================================ import type { SelectionVector } from "./selectionVector"; /** * Array-based SelectionVector for non-sequential selections. * Stores indices explicitly, suitable for irregular patterns and frequent modifications. */ export class FlatSelectionVector implements SelectionVector { /** * @param _selectionVector * @param _limit In write mode the limit of a Buffer is the limit of how much data you can write into the buffer. * In write mode the limit is equal to the capacity of the Buffer. */ constructor( private _selectionVector: number[], private _limit?: number, ) { if (!this._limit) { this._limit = this._selectionVector.length; } } /** @inheritdoc */ getIndex(index: number): number { if (index >= this._limit || index < 0) { throw new RangeError("Index out of bounds"); } return this._selectionVector[index]; } /** @inheritdoc */ setIndex(index: number, value: number): void { if (index >= this._limit || index < 0) { throw new RangeError("Index out of bounds"); } this._selectionVector[index] = value; } /** @inheritdoc */ setLimit(limit: number): void { if (limit < 0 || limit > this.capacity) { throw new RangeError("Limit out of bounds"); } this._limit = limit; } /** @inheritdoc */ selectionValues(): number[] { return this._selectionVector; } /** @inheritdoc */ get capacity() { return this._selectionVector.length; } /** @inheritdoc */ get limit() { return this._limit; } } ================================================ FILE: ts/src/vector/filter/selectionVector.ts ================================================ export interface SelectionVector { getIndex(index: number): number; setIndex(index: number, value: number): void; setLimit(limit: number): void; selectionValues(): number[]; /* Index of the first element that should not be read or written. * It's not the last index that can be accessed, but rather the index that marks the end of * the valid data in the buffer */ get limit(); /* Total size of the buffer */ get capacity(); } ================================================ FILE: ts/src/vector/filter/selectionVectorUtil.spec.ts ================================================ import { describe, it, expect } from "vitest"; import { createSelectionVector, createNullableSelectionVector, updateNullableSelectionVector, } from "./selectionVectorUtils"; import { FlatSelectionVector } from "./flatSelectionVector"; import { SequenceSelectionVector } from "./sequenceSelectionVector"; import BitVector from "../flat/bitVector"; describe("selectionVectorUtils", () => { describe("createSelectionVector", () => { it("Should create a SequenceSelectionVector with given size", () => { const sv = createSelectionVector(5); expect(sv).toBeInstanceOf(SequenceSelectionVector); expect(sv.limit).toBe(5); }); it("Should handle zero size", () => { const sv = createSelectionVector(0); expect(sv).toBeInstanceOf(SequenceSelectionVector); expect(sv.limit).toBe(0); }); }); describe("createNullableSelectionVector", () => { it("Should return all indices when nullabilityBuffer is not provided", () => { const sv = createNullableSelectionVector(5); expect(sv).toBeInstanceOf(FlatSelectionVector); expect(sv.limit).toBe(5); }); it("Should return empty vector when size is 0 and nullabilityBuffer is not provided", () => { const sv = createNullableSelectionVector(0); expect(sv).toBeInstanceOf(FlatSelectionVector); expect(sv.limit).toBe(0); }); it("Should return empty vector when size is 0 with BitVector provided", () => { const buffer = new Uint8Array([0b11111111]); const bitVector = new BitVector(buffer, 8); const sv = createNullableSelectionVector(0, bitVector); expect(sv).toBeInstanceOf(FlatSelectionVector); expect(sv.limit).toBe(0); }); it("Should create FlatSelectionVector with only set bits", () => { const buffer = new Uint8Array([0b00001011]); // bits 0, 1, 3 are set const bitVector = new BitVector(buffer, 8); const sv = createNullableSelectionVector(8, bitVector); expect(sv).toBeInstanceOf(FlatSelectionVector); expect(sv.limit).toBe(3); }); it("Should create empty vector when no bits are set", () => { const buffer = new Uint8Array([0b00000000]); const bitVector = new BitVector(buffer, 8); const sv = createNullableSelectionVector(8, bitVector); expect(sv).toBeInstanceOf(FlatSelectionVector); expect(sv.limit).toBe(0); }); it("Should handle multiple bytes in BitVector", () => { const buffer = new Uint8Array([0b10101010, 0b01010101]); const bitVector = new BitVector(buffer, 16); const sv = createNullableSelectionVector(16, bitVector); expect(sv).toBeInstanceOf(FlatSelectionVector); expect(sv.limit).toBe(8); }); }); describe("updateNullableSelectionVector", () => { describe("with FlatSelectionVector", () => { it("Should return new instance when filtering with BitVector", () => { const selectionVector = new FlatSelectionVector([0, 1, 2, 3, 4, 5, 6, 7]); const buffer = new Uint8Array([0b00001011]); const bitVector = new BitVector(buffer, 8); const result = updateNullableSelectionVector(selectionVector, bitVector); expect(result).toBeInstanceOf(FlatSelectionVector); expect(result.limit).toBe(3); expect(result).not.toBe(selectionVector); }); it("Should return same instance when BitVector is null", () => { const selectionVector = new FlatSelectionVector([0, 1, 2, 3, 4, 5, 6, 7]); const result = updateNullableSelectionVector(selectionVector, null); expect(result).toStrictEqual(selectionVector); }); it("Should return all indices when nullabilityBuffer is undefined", () => { const selectionVector = new FlatSelectionVector([0, 2, 4, 6]); const result = updateNullableSelectionVector(selectionVector, undefined); expect(result).toBeInstanceOf(FlatSelectionVector); expect(result.limit).toBe(4); }); it("Should keep all indices when all bits are set", () => { const selectionVector = new FlatSelectionVector([0, 2, 4, 6]); const buffer = new Uint8Array([0b01010101]); const bitVector = new BitVector(buffer, 8); const result = updateNullableSelectionVector(selectionVector, bitVector); expect(result).toBeInstanceOf(FlatSelectionVector); expect(result.limit).toBe(4); }); it("Should filter out null indices from selection vector", () => { const selectionVector = new FlatSelectionVector([0, 2, 4, 6]); const buffer = new Uint8Array([0b01000101]); // bits at 0, 2, 6 const bitVector = new BitVector(buffer, 8); const result = updateNullableSelectionVector(selectionVector, bitVector); expect(result).toBeInstanceOf(FlatSelectionVector); expect(result.limit).toBe(3); }); it("Should return empty vector when all selected indices are null", () => { const selectionVector = new FlatSelectionVector([1, 3, 5]); const buffer = new Uint8Array([0b01010101]); // bits at 0, 2, 4, 6 (not 1, 3, 5) const bitVector = new BitVector(buffer, 8); const result = updateNullableSelectionVector(selectionVector, bitVector); expect(result).toBeInstanceOf(FlatSelectionVector); expect(result.limit).toBe(0); }); it("Should handle empty FlatSelectionVector", () => { const selectionVector = new FlatSelectionVector([]); const buffer = new Uint8Array([0b11111111]); const bitVector = new BitVector(buffer, 8); const result = updateNullableSelectionVector(selectionVector, bitVector); expect(result).toBeInstanceOf(FlatSelectionVector); expect(result.limit).toBe(0); }); it("Should filter large index values from selection vector", () => { const selectionVector = new FlatSelectionVector([0, 8, 16]); const buffer = new Uint8Array([0b00000001, 0b00000000, 0b00000000]); const bitVector = new BitVector(buffer, 24); const result = updateNullableSelectionVector(selectionVector, bitVector); expect(result).toBeInstanceOf(FlatSelectionVector); expect(result.limit).toBe(1); }); }); describe("with SequenceSelectionVector", () => { it("Should filter SequenceSelectionVector with all bits set", () => { const selectionVector = new SequenceSelectionVector(0, 2, 4); // [0, 2, 4, 6] const buffer = new Uint8Array([0b01010101]); const bitVector = new BitVector(buffer, 8); const result = updateNullableSelectionVector(selectionVector, bitVector); expect(result).toBeInstanceOf(FlatSelectionVector); expect(result.limit).toBe(4); }); it("Should partially filter SequenceSelectionVector", () => { const selectionVector = new SequenceSelectionVector(0, 2, 4); // [0, 2, 4, 6] const buffer = new Uint8Array([0b00010001]); // bits at 0, 4 const bitVector = new BitVector(buffer, 8); const result = updateNullableSelectionVector(selectionVector, bitVector); expect(result).toBeInstanceOf(FlatSelectionVector); expect(result.limit).toBe(2); }); it("Should preserve all SequenceSelectionVector values when nullabilityBuffer is undefined", () => { const selectionVector = new SequenceSelectionVector(1, 3, 3); // [1, 4, 7] const result = updateNullableSelectionVector(selectionVector, undefined); expect(result).toBeInstanceOf(FlatSelectionVector); expect(result.limit).toBe(3); }); it("Should create FlatSelectionVector when BitVector is null", () => { const selectionVector = new SequenceSelectionVector(0, 2, 4); // [0, 2, 4, 6] const result = updateNullableSelectionVector(selectionVector, null); expect(result).toBeInstanceOf(FlatSelectionVector); expect(result.limit).toBe(4); }); }); }); }); ================================================ FILE: ts/src/vector/filter/selectionVectorUtils.ts ================================================ import type { SelectionVector } from "./selectionVector"; import { FlatSelectionVector } from "./flatSelectionVector"; import type BitVector from "../flat/bitVector"; import { SequenceSelectionVector } from "./sequenceSelectionVector"; export function createSelectionVector(size: number) { return new SequenceSelectionVector(0, 1, size); } /** * Creates a selection vector containing indices of non-null values. * @param size - The total number of elements to consider * @param nullabilityBuffer - Optional bit vector where 1=not null, 0=null. If undefined/null, all values are considered non-null. */ export function createNullableSelectionVector(size: number, nullabilityBuffer?: BitVector): SelectionVector { const selectionVector = []; for (let i = 0; i < size; i++) { // Include index if no nullability buffer (all non-null) OR if bit is set (non-null) if (!nullabilityBuffer || nullabilityBuffer.get(i)) { selectionVector.push(i); } } return new FlatSelectionVector(selectionVector); } /** * Filters an existing selection vector to include only non-null values. * @param selectionVector - The input selection vector to filter * @param nullabilityBuffer - Optional bit vector where 1=not null, 0=null. If undefined/null, all values are considered non-null. */ export function updateNullableSelectionVector( selectionVector: SelectionVector, nullabilityBuffer?: BitVector, ): SelectionVector { const filteredIndices = []; for (let i = 0; i < selectionVector.limit; i++) { const vectorIndex = selectionVector.getIndex(i); // Include index if no nullability buffer (all non-null) OR if bit is set (non-null) if (!nullabilityBuffer || nullabilityBuffer.get(vectorIndex)) { filteredIndices.push(vectorIndex); } } return new FlatSelectionVector(filteredIndices); } ================================================ FILE: ts/src/vector/filter/sequenceSelectionVector.spec.ts ================================================ import { describe, it, expect } from "vitest"; import { SequenceSelectionVector } from "./sequenceSelectionVector"; describe("sequenceSelectionVector", () => { describe("getIndex Test", () => { it("Should return sequential values starting from 0", () => { const vector = new SequenceSelectionVector(0, 1, 5); expect(vector.getIndex(0)).toBe(0); expect(vector.getIndex(2)).toBe(2); expect(vector.getIndex(4)).toBe(4); }); it("Should calculate values with custom base and delta", () => { const vector = new SequenceSelectionVector(10, 5, 5); expect(vector.getIndex(0)).toBe(10); expect(vector.getIndex(1)).toBe(15); expect(vector.getIndex(2)).toBe(20); }); it("Should calculate values with negative delta", () => { const vector = new SequenceSelectionVector(100, -10, 5); expect(vector.getIndex(0)).toBe(100); expect(vector.getIndex(1)).toBe(90); expect(vector.getIndex(2)).toBe(80); }); it("Should throw RangeError for out of bounds indices", () => { const vector = new SequenceSelectionVector(0, 1, 5); expect(() => vector.getIndex(80)).toThrowError("Index out of bounds"); expect(() => vector.getIndex(-36)).toThrowError("Index out of bounds"); }); }); describe("setIndex Test", () => { it("Should update value at specified index", () => { const vector = new SequenceSelectionVector(0, 1, 5); vector.setIndex(0, 25); vector.setIndex(2, -48); vector.setIndex(3, 1000000000000001); expect(vector.getIndex(0)).toBe(25); expect(vector.getIndex(2)).toBe(-48); expect(vector.getIndex(3)).toBe(1000000000000001); }); it("Should throw RangeError for out of bounds indices", () => { const vector = new SequenceSelectionVector(0, 1, 5); expect(() => vector.setIndex(-1, 0)).toThrowError("Index out of bounds"); expect(() => vector.setIndex(25, 52)).toThrowError("Index out of bounds"); }); }); describe("limit Test", () => { it("Should initialize limit to size", () => { const vector = new SequenceSelectionVector(0, 1, 5); expect(vector.limit).toBe(5); const emptyVector = new SequenceSelectionVector(0, 1, 0); expect(emptyVector.limit).toBe(0); }); it("Should update limit independently of capacity", () => { const vector = new SequenceSelectionVector(0, 1, 10); expect(vector.capacity).toBe(10); vector.setLimit(3); expect(vector.limit).toBe(3); expect(vector.capacity).toBe(10); vector.setLimit(8); expect(vector.limit).toBe(8); expect(vector.capacity).toBe(10); }); it("Should throw RangeError for negative limit", () => { const vector = new SequenceSelectionVector(0, 1, 5); expect(() => vector.setLimit(-1)).toThrowError("Limit out of bounds"); expect(() => vector.setLimit(100)).toThrowError("Limit out of bounds"); }); it("Should allow setting limit to 0", () => { const vector = new SequenceSelectionVector(0, 1, 5); vector.setLimit(0); expect(vector.limit).toBe(0); }); }); describe("selectionValues Test", () => { it("Should return array with sequential values", () => { const vector = new SequenceSelectionVector(0, 1, 5); const values = vector.selectionValues(); expect(values).toStrictEqual([0, 1, 2, 3, 4]); }); it("Should return empty array for empty vector", () => { const vector = new SequenceSelectionVector(0, 1, 0); expect(vector.selectionValues()).toStrictEqual([]); }); it("Should return array with custom base and delta", () => { const vector = new SequenceSelectionVector(10, 5, 5); expect(vector.selectionValues()).toStrictEqual([10, 15, 20, 25, 30]); }); it("Should return array with negative delta", () => { const vector = new SequenceSelectionVector(100, -10, 5); expect(vector.selectionValues()).toStrictEqual([100, 90, 80, 70, 60]); }); it("Should reflect modified values", () => { const vector = new SequenceSelectionVector(0, 1, 3); vector.setIndex(2, 999); const values = vector.selectionValues(); expect(values).toStrictEqual([0, 1, 999]); }); }); describe("capacity Test", () => { it("Should return capacity equal to size", () => { const vector = new SequenceSelectionVector(0, 1, 5); expect(vector.capacity).toBe(5); }); it("Should return 0 for empty vector", () => { const vector = new SequenceSelectionVector(0, 1, 0); expect(vector.capacity).toBe(0); }); it("Should remain constant when limit changes", () => { const vector = new SequenceSelectionVector(10, 5, 50); expect(vector.capacity).toBe(50); vector.setLimit(25); expect(vector.capacity).toBe(50); }); }); }); ================================================ FILE: ts/src/vector/filter/sequenceSelectionVector.ts ================================================ import type { SelectionVector } from "./selectionVector"; /** * Memory-efficient SelectionVector for arithmetic sequences (base + index * delta). * Calculates values on-demand, only materializes when modified. */ export class SequenceSelectionVector implements SelectionVector { private _materializedArray: number[] | null = null; constructor( private readonly _baseValue: number, private readonly _delta: number, private _limit: number, private readonly _capacity: number = _limit, ) {} /** @inheritdoc */ get limit(): number { return this._limit; } /** @inheritdoc */ get capacity(): number { return this._capacity; } /** @inheritdoc */ selectionValues(): number[] { if (!this._materializedArray) { this._materializedArray = this.materialize(); } return this._materializedArray; } private materialize(): number[] { const arr = new Array(this._capacity); for (let i = 0; i < this._capacity; i++) { arr[i] = this._baseValue + i * this._delta; } return arr; } /** @inheritdoc */ getIndex(index: number): number { if (index >= this._limit || index < 0) { throw new RangeError("Index out of bounds"); } if (this._materializedArray) { return this._materializedArray[index]; } return this._baseValue + index * this._delta; } /** @inheritdoc */ setIndex(index: number, value: number): void { if (index >= this._limit || index < 0) { throw new RangeError("Index out of bounds"); } if (!this._materializedArray) { this._materializedArray = this.materialize(); } this._materializedArray[index] = value; } /** @inheritdoc */ setLimit(limit: number): void { if (limit < 0 || limit > this.capacity) { throw new RangeError("Limit out of bounds"); } this._limit = limit; } } ================================================ FILE: ts/src/vector/fixedSizeVector.ts ================================================ import Vector from "./vector"; export abstract class FixedSizeVector extends Vector {} ================================================ FILE: ts/src/vector/flat/bitVector.ts ================================================ export default class BitVector { private readonly values: Uint8Array; private readonly _size: number; /** * @param values The byte buffer containing the bit values in least-significant bit (LSB) * numbering */ constructor(values: Uint8Array, size: number) { this.values = values; this._size = size; } get(index: number): boolean { const byteIndex = Math.floor(index / 8); const bitIndex = index % 8; const b = this.values[byteIndex]; return ((b >> bitIndex) & 1) === 1; } set(index: number, value: boolean): void { //TODO: refactor -> improve quick and dirty solution const byteIndex = Math.floor(index / 8); const bitIndex = index % 8; this.values[byteIndex] = this.values[byteIndex] | ((value ? 1 : 0) << bitIndex); } getInt(index: number): number { const byteIndex = Math.floor(index / 8); const bitIndex = index % 8; const b = this.values[byteIndex]; return (b >> bitIndex) & 1; } size(): number { return this._size; } getBuffer(): Uint8Array { return this.values; } } ================================================ FILE: ts/src/vector/flat/booleanFlatVector.ts ================================================ import type BitVector from "./bitVector"; import Vector from "../vector"; export class BooleanFlatVector extends Vector { private readonly dataVector: BitVector; constructor(name: string, dataVector: BitVector, sizeOrNullabilityBuffer: number | BitVector) { super(name, dataVector.getBuffer(), sizeOrNullabilityBuffer); this.dataVector = dataVector; } protected getValueFromBuffer(index: number): boolean { return this.dataVector.get(index); } } ================================================ FILE: ts/src/vector/flat/doubleFlatVector.ts ================================================ import { FixedSizeVector } from "../fixedSizeVector"; export class DoubleFlatVector extends FixedSizeVector { protected getValueFromBuffer(index: number): number { return this.dataBuffer[index]; } } ================================================ FILE: ts/src/vector/flat/floatFlatVector.spec.ts ================================================ import { describe, it, expect } from "vitest"; import { FloatFlatVector } from "./floatFlatVector"; describe("FloatFlatVector", () => { it("should construct and return values correctly", () => { const data = new Float32Array([1.5, 2.5, 3.5, 4.5, 5.5]); const vec = new FloatFlatVector("test", data, data.length); expect(vec.name).toBe("test"); expect(vec.size).toBe(5); expect(vec.getValue(0)).toBe(1.5); expect(vec.getValue(2)).toBe(3.5); expect(vec.getValue(4)).toBe(5.5); }); }); ================================================ FILE: ts/src/vector/flat/floatFlatVector.ts ================================================ import { FixedSizeVector } from "../fixedSizeVector"; export class FloatFlatVector extends FixedSizeVector { protected getValueFromBuffer(index: number): number { return this.dataBuffer[index]; } } ================================================ FILE: ts/src/vector/flat/int32FlatVector.spec.ts ================================================ import { describe, it, expect } from "vitest"; import { Int32FlatVector } from "./int32FlatVector"; describe("Int32FlatVector", () => { it("should construct and return values correctly", () => { const data = new Int32Array([10, 20, 30, 40, 50]); const vec = new Int32FlatVector("test", data, data.length); expect(vec.name).toBe("test"); expect(vec.size).toBe(5); expect(vec.has(0)).toBe(true); expect(vec.getValue(0)).toBe(10); expect(vec.getValue(2)).toBe(30); expect(vec.getValue(4)).toBe(50); }); }); ================================================ FILE: ts/src/vector/flat/int32FlatVector.ts ================================================ import { FixedSizeVector } from "../fixedSizeVector"; export class Int32FlatVector extends FixedSizeVector { protected getValueFromBuffer(index: number): number { return this.dataBuffer[index]; } } ================================================ FILE: ts/src/vector/flat/int64FlatVector.spec.ts ================================================ import { describe, it, expect } from "vitest"; import { Int64FlatVector } from "./int64FlatVector"; describe("Int64FlatVector", () => { it("should construct and return values correctly", () => { const data = new BigInt64Array([10n, 20n, 30n, 40n, 50n]); const vec = new Int64FlatVector("test", data, data.length); expect(vec.name).toBe("test"); expect(vec.size).toBe(5); expect(vec.getValue(0)).toBe(10n); expect(vec.getValue(2)).toBe(30n); expect(vec.getValue(4)).toBe(50n); }); }); ================================================ FILE: ts/src/vector/flat/int64FlatVector.ts ================================================ import { FixedSizeVector } from "../fixedSizeVector"; export class Int64FlatVector extends FixedSizeVector { protected getValueFromBuffer(index: number): bigint { return this.dataBuffer[index]; } } ================================================ FILE: ts/src/vector/flat/stringFlatVector.ts ================================================ import { VariableSizeVector } from "../variableSizeVector"; import type BitVector from "./bitVector"; import { decodeString } from "../../decoding/decodingUtils"; export class StringFlatVector extends VariableSizeVector { constructor(name: string, offsetBuffer: Uint32Array, dataBuffer: Uint8Array, nullabilityBuffer?: BitVector) { super(name, offsetBuffer, dataBuffer, nullabilityBuffer ?? offsetBuffer.length - 1); } protected getValueFromBuffer(index: number): string { const start = this.offsetBuffer[index]; const end = this.offsetBuffer[index + 1]; return decodeString(this.dataBuffer, start, end); } } ================================================ FILE: ts/src/vector/fsst-dictionary/stringFsstDictionaryVector.spec.ts ================================================ import { describe, it, expect, beforeEach } from "vitest"; import BitVector from "../flat/bitVector"; import { StringFsstDictionaryVector } from "./stringFsstDictionaryVector"; describe("StringFsstDictionaryVector", () => { let indexBuffer: Uint32Array; let offsetBuffer: Uint32Array; let dictionaryBuffer: Uint8Array; let symbolOffsetBuffer: Uint32Array; let symbolTableBuffer: Uint8Array; let nullabilityBuffer: BitVector; beforeEach(() => { indexBuffer = new Uint32Array([0, 1, 2]); offsetBuffer = new Uint32Array([0, 5, 10]); dictionaryBuffer = new Uint8Array([ /* mock data */ ]); symbolOffsetBuffer = new Uint32Array([0, 3, 6]); symbolTableBuffer = new Uint8Array([ /* mock data */ ]); nullabilityBuffer = new BitVector(new Uint8Array([0b00000001]), 2); }); it("should create an instance of StringFsstDictionaryVector", () => { const vector = new StringFsstDictionaryVector( "testVector", indexBuffer, offsetBuffer, dictionaryBuffer, symbolOffsetBuffer, symbolTableBuffer, nullabilityBuffer, ); expect(vector).toBeInstanceOf(StringFsstDictionaryVector); }); }); ================================================ FILE: ts/src/vector/fsst-dictionary/stringFsstDictionaryVector.ts ================================================ import { VariableSizeVector } from "../variableSizeVector"; import type BitVector from "../flat/bitVector"; import { decodeFsst } from "../../decoding/fsstDecoder"; import { decodeString } from "../../decoding/decodingUtils"; export class StringFsstDictionaryVector extends VariableSizeVector { // TODO: extend from StringVector private symbolLengthBuffer: Uint32Array; private decodedDictionary: Uint8Array; constructor( name: string, private readonly indexBuffer: Uint32Array, offsetBuffer: Uint32Array, dictionaryBuffer: Uint8Array, private readonly symbolOffsetBuffer: Uint32Array, private readonly symbolTableBuffer: Uint8Array, nullabilityBuffer: BitVector, ) { super(name, offsetBuffer, dictionaryBuffer, nullabilityBuffer ?? indexBuffer.length); } protected getValueFromBuffer(index: number): string { if (this.decodedDictionary == null) { if (this.symbolLengthBuffer == null) { // TODO: change FsstEncoder to take offsets instead of length to get rid of this conversion this.symbolLengthBuffer = this.offsetToLengthBuffer(this.symbolOffsetBuffer); } this.decodedDictionary = decodeFsst(this.symbolTableBuffer, this.symbolLengthBuffer, this.dataBuffer); } const offset = this.indexBuffer[index]; const start = this.offsetBuffer[offset]; const end = this.offsetBuffer[offset + 1]; return decodeString(this.decodedDictionary, start, end); } // TODO: get rid of that conversion private offsetToLengthBuffer(offsetBuffer: Uint32Array): Uint32Array { const lengthBuffer = new Uint32Array(offsetBuffer.length - 1); let previousOffset = offsetBuffer[0]; for (let i = 1; i < offsetBuffer.length; i++) { const offset = offsetBuffer[i]; lengthBuffer[i - 1] = offset - previousOffset; previousOffset = offset; } return lengthBuffer; } } ================================================ FILE: ts/src/vector/geometry/constGeometryVector.ts ================================================ import { GeometryVector, type MortonSettings } from "./geometryVector"; import { GEOMETRY_TYPE } from "./geometryType"; import { VertexBufferType } from "./vertexBufferType"; import type { TopologyVector } from "../../vector/geometry/topologyVector"; export function createConstGeometryVector( numGeometries: number, geometryType: number, topologyVector: TopologyVector, vertexOffsets: Uint32Array | undefined, vertexBuffer: Int32Array | Uint32Array, ): ConstGeometryVector { return new ConstGeometryVector( numGeometries, geometryType, VertexBufferType.VEC_2, topologyVector, vertexOffsets, vertexBuffer, ); } export function createMortonEncodedConstGeometryVector( numGeometries: number, geometryType: number, topologyVector: TopologyVector, vertexOffsets: Uint32Array | undefined, vertexBuffer: Int32Array | Uint32Array, mortonInfo: MortonSettings, ): ConstGeometryVector { return new ConstGeometryVector( numGeometries, geometryType, VertexBufferType.MORTON, topologyVector, vertexOffsets, vertexBuffer, mortonInfo, ); } export class ConstGeometryVector extends GeometryVector { constructor( private readonly _numGeometries: number, private readonly _geometryType: number, vertexBufferType: VertexBufferType, topologyVector: TopologyVector, vertexOffsets: Uint32Array | undefined, vertexBuffer: Int32Array | Uint32Array, mortonSettings?: MortonSettings, ) { super(vertexBufferType, topologyVector, vertexOffsets, vertexBuffer, mortonSettings); } geometryType(_index: number): number { return this._geometryType; } get numGeometries(): number { return this._numGeometries; } containsPolygonGeometry(): boolean { return this._geometryType === GEOMETRY_TYPE.POLYGON || this._geometryType === GEOMETRY_TYPE.MULTIPOLYGON; } containsSingleGeometryType(): boolean { return true; } } ================================================ FILE: ts/src/vector/geometry/constGpuVector.ts ================================================ import { GpuVector } from "./gpuVector"; import type { TopologyVector } from "./topologyVector"; export function createConstGpuVector( numGeometries: number, geometryType: number, triangleOffsets: Uint32Array, indexBuffer: Uint32Array, vertexBuffer: Int32Array | Uint32Array, topologyVector?: TopologyVector, ): GpuVector { return new ConstGpuVector(numGeometries, geometryType, triangleOffsets, indexBuffer, vertexBuffer, topologyVector); } //TODO: extend from GeometryVector -> make topology vector optional export class ConstGpuVector extends GpuVector { constructor( private readonly _numGeometries: number, private readonly _geometryType: number, triangleOffsets: Uint32Array, indexBuffer: Uint32Array, vertexBuffer: Int32Array | Uint32Array, topologyVector?: TopologyVector, ) { super(triangleOffsets, indexBuffer, vertexBuffer, topologyVector); } geometryType(_index: number): number { return this._geometryType; } get numGeometries(): number { return this._numGeometries; } containsSingleGeometryType(): boolean { return true; } } ================================================ FILE: ts/src/vector/geometry/flatGeometryVector.ts ================================================ import { GeometryVector, type MortonSettings } from "./geometryVector"; import { GEOMETRY_TYPE } from "./geometryType"; import { VertexBufferType } from "./vertexBufferType"; import type { TopologyVector } from "../../vector/geometry/topologyVector"; export function createFlatGeometryVector( geometryTypes: Uint32Array, topologyVector: TopologyVector, vertexOffsets: Uint32Array | undefined, vertexBuffer: Int32Array | Uint32Array, ): FlatGeometryVector { return new FlatGeometryVector(VertexBufferType.VEC_2, geometryTypes, topologyVector, vertexOffsets, vertexBuffer); } export function createFlatGeometryVectorMortonEncoded( geometryTypes: Uint32Array, topologyVector: TopologyVector, vertexOffsets: Uint32Array | undefined, vertexBuffer: Int32Array | Uint32Array, mortonInfo: MortonSettings, ): FlatGeometryVector { return new FlatGeometryVector( VertexBufferType.MORTON, geometryTypes, topologyVector, vertexOffsets, vertexBuffer, mortonInfo, ); } export class FlatGeometryVector extends GeometryVector { constructor( vertexBufferType: VertexBufferType, private readonly _geometryTypes: Uint32Array, topologyVector: TopologyVector, vertexOffsets: Uint32Array | undefined, vertexBuffer: Int32Array | Uint32Array, mortonSettings?: MortonSettings, ) { super(vertexBufferType, topologyVector, vertexOffsets, vertexBuffer, mortonSettings); } geometryType(index: number): number { return this._geometryTypes[index]; } get numGeometries(): number { return this._geometryTypes.length; } containsPolygonGeometry(): boolean { for (let i = 0; i < this.numGeometries; i++) { if (this.geometryType(i) === GEOMETRY_TYPE.POLYGON || this.geometryType(i) === GEOMETRY_TYPE.MULTIPOLYGON) { return true; } } return false; } containsSingleGeometryType(): boolean { return false; } } ================================================ FILE: ts/src/vector/geometry/flatGpuVector.ts ================================================ import { GpuVector } from "./gpuVector"; import type { TopologyVector } from "./topologyVector"; export function createFlatGpuVector( geometryTypes: Uint32Array, triangleOffsets: Uint32Array, indexBuffer: Uint32Array, vertexBuffer: Int32Array | Uint32Array, topologyVector?: TopologyVector, ): GpuVector { return new FlatGpuVector(geometryTypes, triangleOffsets, indexBuffer, vertexBuffer, topologyVector); } //TODO: extend from GeometryVector -> make topology vector optional export class FlatGpuVector extends GpuVector { constructor( private readonly _geometryTypes: Uint32Array, triangleOffsets: Uint32Array, indexBuffer: Uint32Array, vertexBuffer: Int32Array | Uint32Array, topologyVector?: TopologyVector, ) { super(triangleOffsets, indexBuffer, vertexBuffer, topologyVector); } geometryType(index: number): number { return this._geometryTypes[index]; } get numGeometries(): number { return this._geometryTypes.length; } containsSingleGeometryType(): boolean { return false; } } ================================================ FILE: ts/src/vector/geometry/geometryType.ts ================================================ export enum GEOMETRY_TYPE { POINT = 0, LINESTRING = 1, POLYGON = 2, MULTIPOINT = 3, MULTILINESTRING = 4, MULTIPOLYGON = 5, } export enum SINGLE_PART_GEOMETRY_TYPE { POINT = 0, LINESTRING = 1, POLYGON = 2, } ================================================ FILE: ts/src/vector/geometry/geometryVector.ts ================================================ import { convertGeometryVector } from "./geometryVectorConverter"; import { decodeZOrderCurve } from "./zOrderCurve"; import type Point from "@mapbox/point-geometry"; import type { GEOMETRY_TYPE } from "./geometryType"; import type { VertexBufferType } from "./vertexBufferType"; import type { TopologyVector } from "../../vector/geometry/topologyVector"; export type CoordinatesArray = Array>; export type Geometry = { coordinates: CoordinatesArray; type: GEOMETRY_TYPE; }; export interface MortonSettings { numBits: number; coordinateShift: number; } export abstract class GeometryVector { protected constructor( private readonly _vertexBufferType: VertexBufferType, private readonly _topologyVector: TopologyVector, private readonly _vertexOffsets: Uint32Array | undefined, private readonly _vertexBuffer: Int32Array | Uint32Array, private readonly _mortonSettings?: MortonSettings, ) {} get vertexBufferType(): VertexBufferType { return this._vertexBufferType; } get topologyVector(): TopologyVector { return this._topologyVector; } get vertexOffsets(): Uint32Array | undefined { return this._vertexOffsets; } get vertexBuffer(): Int32Array | Uint32Array { return this._vertexBuffer; } /* Allows faster access to the vertices since morton encoding is currently not used in the POC. Morton encoding will be used after adapting the shader to decode the morton codes on the GPU. */ getSimpleEncodedVertex(index: number): [number, number] { const offset = this.vertexOffsets ? this.vertexOffsets[index] * 2 : index * 2; const x = this.vertexBuffer[offset]; const y = this.vertexBuffer[offset + 1]; return [x, y]; } //TODO: add scaling information to the constructor getVertex(index: number): [number, number] { if (this.vertexOffsets && this.mortonSettings) { //TODO: move decoding of the morton codes on the GPU in the vertex shader const vertexOffset = this.vertexOffsets[index]; const mortonEncodedVertex = this.vertexBuffer[vertexOffset]; //TODO: improve performance -> inline calculation and move to decoding of VertexBuffer const vertex = decodeZOrderCurve( mortonEncodedVertex, this.mortonSettings.numBits, this.mortonSettings.coordinateShift, ); return [vertex.x, vertex.y]; } const offset = this.vertexOffsets ? this.vertexOffsets[index] * 2 : index * 2; const x = this.vertexBuffer[offset]; const y = this.vertexBuffer[offset + 1]; return [x, y]; } getGeometries(): CoordinatesArray[] { return convertGeometryVector(this); } get mortonSettings(): MortonSettings | undefined { return this._mortonSettings; } abstract containsPolygonGeometry(): boolean; abstract geometryType(index: number): number; abstract get numGeometries(): number; abstract containsSingleGeometryType(): boolean; } ================================================ FILE: ts/src/vector/geometry/geometryVectorConverter.spec.ts ================================================ import Point from "@mapbox/point-geometry"; import { describe, it, expect } from "vitest"; import { convertGeometryVector } from "./geometryVectorConverter"; import { GEOMETRY_TYPE } from "./geometryType"; import { VertexBufferType } from "./vertexBufferType"; import { encodeZOrderCurve } from "../../encoding/zOrderCurveEncoder"; import type { GeometryVector, MortonSettings } from "./geometryVector"; import { encodeLineStringGeometryVector, encodeLineStringGeometryVectorWithMortonEncoding, encodeMultiLineStringGeometryVector, encodeMultiLineStringGeometryVectorWithMortonOffsets, encodeMultiLineStringGeometryVectorWithOffsets, encodeMultiPointGeometryVector, encodeMultiPolygonGeometryVector, encodeMultiPolygonGeometryVectorWithMortonOffsets, encodeMultiPolygonGeometryVectorWithOffsets, encodePointGeometryVector, encodePointGeometryVectorWithMortonEncoding, encodePointGeometryVectorWithOffset, encodePointsGeometryVector, encodePolygonGeometryVector, encodePolygonGeometryVectorWithMortonOffsets, encodePolygonGeometryVectorWithOffsets, } from "../../encoding/constGeometryVectorEncoder"; import { ConstGeometryVector } from "./constGeometryVector"; import { FlatGeometryVector } from "./flatGeometryVector"; describe("POINT – sequential vertex buffer (no vertexOffsets)", () => { it("creates a single point from the vertex buffer", () => { const x = 5; const y = 7; const gv = encodePointGeometryVector(x, y); const result = convertGeometryVector(gv); expect(result).toHaveLength(1); expect(result[0]).toEqual([[new Point(x, y)]]); }); it("creates multiple points sequentially", () => { const gv = encodePointsGeometryVector([1, 2, 3, 4]); const result = convertGeometryVector(gv); expect(result[0]).toEqual([[new Point(1, 2)]]); expect(result[1]).toEqual([[new Point(3, 4)]]); }); }); describe("POINT – VEC_2 dictionary encoded", () => { it("reads point via vertexOffsets with VEC_2 buffer type", () => { // vertexOffsets[0] = 1 → vertexBuffer[2], vertexBuffer[3] const x = 42; const y = 55; const gv = encodePointGeometryVectorWithOffset(x, y); const result = convertGeometryVector(gv); expect(result[0]).toEqual([[new Point(x, y)]]); }); }); describe("POINT – Morton dictionary encoded", () => { it("decodes a point via morton encoding", () => { const x = 3; const y = 7; const gv = encodePointGeometryVectorWithMortonEncoding(x, y); const result = convertGeometryVector(gv); expect(result[0]).toEqual([[new Point(x, y)]]); }); it("decodes a point with a non-zero coordinateShift", () => { const x = 50; const y = 80; const settings: MortonSettings = { numBits: 16, coordinateShift: 100 } as MortonSettings; const code = encodeZOrderCurve(x, y, settings.numBits, settings.coordinateShift); const gv = new ConstGeometryVector( 1, GEOMETRY_TYPE.POINT, VertexBufferType.MORTON, { geometryOffsets: undefined, partOffsets: undefined, ringOffsets: undefined, }, new Uint32Array([0]), new Int32Array([code]), settings, ); const result = convertGeometryVector(gv); expect(result[0]).toEqual([[new Point(x, y)]]); }); }); describe("MULTIPOINT – sequential vertex buffer", () => { it("creates a multi-point geometry", () => { const gv = encodeMultiPointGeometryVector([ [1, 2], [3, 4], [5, 6], ]); const result = convertGeometryVector(gv); expect(result[0]).toEqual([[new Point(1, 2)], [new Point(3, 4)], [new Point(5, 6)]]); }); }); describe("MULTIPOINT – VEC_2 dictionary encoded", () => { it("reads multi-point via vertexOffsets", () => { // offsets [0, 2] → vertexBuffer[0,1] and vertexBuffer[4,5] const gv = new ConstGeometryVector( 1, GEOMETRY_TYPE.MULTIPOINT, VertexBufferType.VEC_2, { geometryOffsets: new Uint32Array([0, 2]), partOffsets: undefined, ringOffsets: undefined, }, new Uint32Array([0, 2]), new Int32Array([10, 20, 99, 99, 30, 40]), ); const result = convertGeometryVector(gv); expect(result[0]).toEqual([[new Point(10, 20)], [new Point(30, 40)]]); }); }); describe("LINESTRING – sequential vertex buffer, no polygon context", () => { it("creates a line string from sequential vertices", () => { const gv = encodeLineStringGeometryVector([ [0, 0], [1, 1], [2, 2], ]); const result = convertGeometryVector(gv); expect(result[0]).toEqual([[new Point(0, 0), new Point(1, 1), new Point(2, 2)]]); }); }); describe("LINESTRING – sequential vertex buffer, polygon context (uses ringOffsets)", () => { it("creates a line string using ringOffsets when containsPolygon is true", () => { const gv = { numGeometries: 1, vertexBuffer: new Int32Array([1, 2, 3, 4]), vertexOffsets: undefined, topologyVector: { geometryOffsets: undefined, partOffsets: new Uint32Array([0, 1]), ringOffsets: new Uint32Array([0, 2]), }, geometryType: () => GEOMETRY_TYPE.LINESTRING, containsPolygonGeometry: () => true, } as any as GeometryVector; const result = convertGeometryVector(gv); expect(result[0]).toEqual([[new Point(1, 2), new Point(3, 4)]]); }); }); describe("LINESTRING – VEC_2 dictionary encoded", () => { it("decodes a line string via vertexOffsets VEC_2", () => { // offsets [0, 1] → vertexBuffer[0,1] and [2,3] const gv = { numGeometries: 1, vertexBuffer: new Int32Array([5, 10, 15, 20]), vertexOffsets: new Uint32Array([0, 1]), vertexBufferType: VertexBufferType.VEC_2, topologyVector: { geometryOffsets: undefined, partOffsets: new Uint32Array([0, 2]), ringOffsets: undefined, }, geometryType: () => GEOMETRY_TYPE.LINESTRING, containsPolygonGeometry: () => false, } as any as GeometryVector; const result = convertGeometryVector(gv); expect(result[0]).toEqual([[new Point(5, 10), new Point(15, 20)]]); }); }); describe("LINESTRING – Morton dictionary encoded", () => { it("decodes a line string via Morton encoding", () => { const gv = encodeLineStringGeometryVectorWithMortonEncoding([ [1, 2], [3, 4], [5, 6], ]); const result = convertGeometryVector(gv); expect(result[0]).toEqual([[new Point(1, 2), new Point(3, 4), new Point(5, 6)]]); }); }); describe("POLYGON – sequential vertex buffer, no holes", () => { it("creates a polygon shell that is closed", () => { const gv = encodePolygonGeometryVector([ [ [0, 0], [10, 0], [10, 10], [0, 10], ], ]); const result = convertGeometryVector(gv); const shell = result[0][0]; expect(shell).toHaveLength(5); // 4 + closed expect(shell[0]).toEqual(shell[4]); }); }); describe("POLYGON – sequential vertex buffer, with hole", () => { it("creates a polygon with one hole", () => { const gv = encodePolygonGeometryVector([ [ [0, 0], [10, 0], [0, 10], ], [ [2, 2], [4, 2], [2, 4], ], ]); const result = convertGeometryVector(gv); expect(result[0]).toHaveLength(2); expect(result[0][0]).toHaveLength(4); // shell has 3 points + 1 (closing point) expect(result[0][1]).toHaveLength(4); // hole has 3 points + 1 (closing point) }); }); describe("POLYGON – VEC_2 dictionary encoded, no holes", () => { it("creates a closed polygon shell via VEC_2 vertex offsets", () => { const gv = encodePolygonGeometryVectorWithOffsets([ [ [0, 0], [5, 5], [10, 0], ], ]); const result = convertGeometryVector(gv); const shell = result[0][0]; expect(shell).toHaveLength(4); // closed expect(shell[0]).toEqual(shell[3]); }); }); describe("POLYGON – VEC_2 dictionary encoded, with hole", () => { it("creates a polygon with one hole via VEC_2 vertex offsets", () => { const gv = encodePolygonGeometryVectorWithOffsets([ [ [0, 0], [10, 0], [0, 10], ], [ [2, 2], [4, 2], [2, 4], ], ]); const result = convertGeometryVector(gv); expect(result[0]).toHaveLength(2); expect(result[0][0]).toHaveLength(4); // shell has 3 points + 1 (closing point) expect(result[0][1]).toHaveLength(4); // hole has 3 points + 1 (closing point) }); }); describe("POLYGON – Morton dictionary encoded, no holes", () => { it("creates a closed polygon shell via Morton encoding", () => { const gv = encodePolygonGeometryVectorWithMortonOffsets([ [ [0, 0], [5, 0], [0, 5], ], ]); const result = convertGeometryVector(gv); const shell = result[0][0]; expect(shell).toHaveLength(4); expect(shell[0]).toEqual(new Point(0, 0)); expect(shell[1]).toEqual(new Point(5, 0)); expect(shell[2]).toEqual(new Point(0, 5)); expect(shell[3]).toEqual(shell[0]); // closed }); }); describe("POLYGON – Morton dictionary encoded, with hole", () => { it("creates a polygon with one hole via Morton encoding", () => { const gv = encodePolygonGeometryVectorWithMortonOffsets([ [ [0, 0], [10, 0], [0, 10], ], [ [1, 1], [3, 1], [1, 3], ], ]); const result = convertGeometryVector(gv); expect(result[0]).toHaveLength(2); expect(result[0][0]).toHaveLength(4); // shell has 3 points + 1 (closing point) expect(result[0][1]).toHaveLength(4); // hole has 3 points + 1 (closing point) }); }); describe("MULTILINESTRING – sequential vertex buffer, no polygon context", () => { it("creates a multi-line string with two line strings", () => { const gv = encodeMultiLineStringGeometryVector([ [ [0, 0], [1, 1], ], [ [2, 2], [3, 3], [4, 4], ], [ [5, 5], [6, 6], ], ]); const result = convertGeometryVector(gv); expect(result[0]).toHaveLength(3); expect(result[0][0]).toEqual([new Point(0, 0), new Point(1, 1)]); expect(result[0][1]).toEqual([new Point(2, 2), new Point(3, 3), new Point(4, 4)]); expect(result[0][2]).toEqual([new Point(5, 5), new Point(6, 6)]); }); }); describe("MULTILINESTRING – VEC_2 dictionary encoded", () => { it("decodes multi-line string via vertexOffsets VEC_2", () => { const gv = encodeMultiLineStringGeometryVectorWithOffsets([ [ [0, 1], [2, 3], ], [ [4, 5], [6, 7], ], ]); const result = convertGeometryVector(gv); expect(result[0]).toHaveLength(2); expect(result[0][0]).toEqual([new Point(0, 1), new Point(2, 3)]); expect(result[0][1]).toEqual([new Point(4, 5), new Point(6, 7)]); }); }); describe("MULTILINESTRING – Morton dictionary encoded", () => { it("decodes multi-line string via Morton encoding", () => { const gv = encodeMultiLineStringGeometryVectorWithMortonOffsets([ [ [1, 2], [3, 4], ], ]); const result = convertGeometryVector(gv); expect(result[0][0]).toEqual([new Point(1, 2), new Point(3, 4)]); }); }); describe("MULTIPOLYGON – sequential vertex buffer, no holes", () => { it("creates a multi-polygon with two triangular polygons", () => { const gv = encodeMultiPolygonGeometryVector([ [ [ [0, 0], [1, 0], [0, 1], ], ], [ [ [5, 5], [6, 5], [5, 6], ], ], [ [ [10, 10], [10, 0], [0, 10], ], ], ]); const result = convertGeometryVector(gv); expect(result[0]).toHaveLength(3); expect(result[0][0]).toHaveLength(4); // closed shell expect(result[0][1]).toHaveLength(4); // closed shell expect(result[0][2]).toHaveLength(4); // closed shell }); }); describe("MULTIPOLYGON – sequential vertex buffer, with holes", () => { it("creates a polygon that has a hole", () => { const gv = encodeMultiPolygonGeometryVector([ [ [ [0, 0], [10, 0], [0, 10], ], [ [1, 1], [3, 1], [1, 3], ], ], [ [ [5, 5], [25, 5], [5, 25], ], [ [6, 6], [7, 6], [6, 7], ], ], [ [ [10, 10], [10, 0], [0, 10], ], ], ]); const result = convertGeometryVector(gv); expect(result[0]).toHaveLength(5); expect(result[0][0]).toHaveLength(4); expect(result[0][1]).toHaveLength(4); expect(result[0][2]).toHaveLength(4); expect(result[0][3]).toHaveLength(4); expect(result[0][4]).toHaveLength(4); }); }); describe("MULTIPOLYGON – VEC_2 dictionary encoded, no holes", () => { it("decodes multi-polygon via vertexOffsets VEC_2", () => { const gv = encodeMultiPolygonGeometryVectorWithOffsets([ [ [ [0, 0], [2, 0], [0, 2], ], ], [ [ [5, 5], [7, 5], [5, 7], ], ], ]); const result = convertGeometryVector(gv); expect(result[0]).toHaveLength(2); expect(result[0][0]).toHaveLength(4); // closed }); }); describe("MULTIPOLYGON – VEC_2 dictionary encoded, with holes", () => { it("creates a multi-polygon with hole via VEC_2", () => { const gv = encodeMultiPolygonGeometryVectorWithOffsets([ [ [ [0, 0], [10, 0], [0, 10], ], [ [1, 1], [3, 1], [1, 3], ], ], ]); const result = convertGeometryVector(gv); expect(result[0]).toHaveLength(2); }); }); describe("MULTIPOLYGON – Morton dictionary encoded, no holes", () => { it("decodes multi-polygon shells via Morton encoding", () => { const gv = encodeMultiPolygonGeometryVectorWithMortonOffsets([ [ [ [0, 0], [1, 0], [0, 1], ], ], ]); const result = convertGeometryVector(gv); const shell = result[0][0]; expect(shell).toHaveLength(4); // closed expect(shell[0]).toEqual(new Point(0, 0)); expect(shell[3]).toEqual(shell[0]); }); }); describe("MULTIPOLYGON – Morton dictionary encoded, with holes", () => { it("creates a multi-polygon with hole via Morton encoding", () => { const gv = encodeMultiPolygonGeometryVectorWithMortonOffsets([ [ [ [0, 0], [10, 0], [0, 10], ], [ [1, 1], [3, 1], [1, 3], ], ], ]); const result = convertGeometryVector(gv); expect(result[0]).toHaveLength(2); }); }); describe("Vector with multiple geometries of different types", () => { it("converts geometries of different types in the same vector", () => { const pointGv = encodePointGeometryVector(1, 2); const multiPointGv = encodeMultiPointGeometryVector([ [3, 4], [5, 6], ]); const lineStringGv = encodeLineStringGeometryVector([ [7, 8], [9, 10], ]); const polygonGv = encodePolygonGeometryVector([ [ [11, 11], [11, 12], [12, 11], ], ]); const multiLineStringGv = encodeMultiLineStringGeometryVector([ [ [13, 14], [15, 16], ], ]); const gv = new FlatGeometryVector( VertexBufferType.VEC_2, new Uint32Array([ GEOMETRY_TYPE.POINT, GEOMETRY_TYPE.MULTIPOINT, GEOMETRY_TYPE.LINESTRING, GEOMETRY_TYPE.POLYGON, GEOMETRY_TYPE.MULTILINESTRING, ]), { geometryOffsets: new Uint32Array([0, 1, 3, 5, 8, 9]), partOffsets: new Uint32Array([0, 1, 2, 3, 4, 5, 6]), ringOffsets: new Uint32Array([0, 1, 2, 3, 5, 8, 10]), }, undefined, new Int32Array([ ...pointGv.vertexBuffer, ...multiPointGv.vertexBuffer, ...lineStringGv.vertexBuffer, ...polygonGv.vertexBuffer, ...multiLineStringGv.vertexBuffer, ]), ); const result = convertGeometryVector(gv); expect(result).toHaveLength(5); expect(result[0]).toEqual([[new Point(1, 2)]]); expect(result[1]).toEqual([[new Point(3, 4)], [new Point(5, 6)]]); expect(result[2]).toEqual([[new Point(7, 8), new Point(9, 10)]]); expect(result[3]).toEqual([ [ new Point(11, 11), new Point(11, 12), new Point(12, 11), new Point(11, 11), // closed ], ]); expect(result[4]).toEqual([[new Point(13, 14), new Point(15, 16)]]); }); }); describe("Error handling", () => { it("throws on unsupported geometry type", () => { const gv = { numGeometries: 1, topologyVector: {}, geometryType: () => 999 as unknown as GEOMETRY_TYPE, containsPolygonGeometry: () => false, } as unknown as GeometryVector; expect(() => convertGeometryVector(gv)).toThrowError("The specified geometry type is currently not supported."); }); }); describe("Edge cases", () => { it("returns an empty array when numGeometries is 0", () => { const gv = { numGeometries: 0, topologyVector: {}, containsPolygonGeometry: () => false, } as unknown as GeometryVector; expect(convertGeometryVector(gv)).toHaveLength(0); }); it("treats vertexOffsets with length 0 as absent (sequential buffer path)", () => { const gv = new ConstGeometryVector( 1, GEOMETRY_TYPE.POINT, VertexBufferType.VEC_2, { geometryOffsets: undefined, partOffsets: undefined, ringOffsets: undefined, }, new Uint32Array([]), // length === 0 → same as undefined new Int32Array([3, 9]), ); const result = convertGeometryVector(gv); expect(result[0]).toEqual([[new Point(3, 9)]]); }); }); ================================================ FILE: ts/src/vector/geometry/geometryVectorConverter.ts ================================================ import type { GeometryVector, MortonSettings, CoordinatesArray } from "./geometryVector"; import { decodeZOrderCurve } from "./zOrderCurve"; import { GEOMETRY_TYPE } from "./geometryType"; import { VertexBufferType } from "./vertexBufferType"; import Point from "@mapbox/point-geometry"; export function convertGeometryVector(geometryVector: GeometryVector): CoordinatesArray[] { const geometries: CoordinatesArray[] = new Array(geometryVector.numGeometries); let partOffsetCounter = 1; let ringOffsetsCounter = 1; let geometryOffsetsCounter = 1; let geometryCounter = 0; let vertexBufferOffset = 0; let vertexOffsetsOffset = 0; const mortonSettings = geometryVector.mortonSettings; const topologyVector = geometryVector.topologyVector; const geometryOffsets = topologyVector.geometryOffsets; const partOffsets = topologyVector.partOffsets; const ringOffsets = topologyVector.ringOffsets; const vertexOffsets = geometryVector.vertexOffsets; const nonOffset = !vertexOffsets || vertexOffsets.length === 0; const containsPolygon = geometryVector.containsPolygonGeometry(); const vertexBuffer = geometryVector.vertexBuffer; for (let i = 0; i < geometryVector.numGeometries; i++) { const geometryType = geometryVector.geometryType(i); switch (geometryType) { case GEOMETRY_TYPE.POINT: { let x: number; let y: number; if (nonOffset) { x = vertexBuffer[vertexBufferOffset++]; y = vertexBuffer[vertexBufferOffset++]; } else if (geometryVector.vertexBufferType === VertexBufferType.MORTON) { const offset = vertexOffsets[vertexOffsetsOffset++]; const mortonCode = vertexBuffer[offset]; const vertex = decodeZOrderCurve( mortonCode, mortonSettings.numBits, mortonSettings.coordinateShift, ); x = vertex.x; y = vertex.y; } else { const offset = vertexOffsets[vertexOffsetsOffset++] * 2; x = vertexBuffer[offset]; y = vertexBuffer[offset + 1]; } geometries[geometryCounter++] = [[new Point(x, y)]]; if (geometryOffsets) geometryOffsetsCounter++; if (partOffsets) partOffsetCounter++; if (ringOffsets) ringOffsetsCounter++; } break; case GEOMETRY_TYPE.MULTIPOINT: { const numPoints = geometryOffsets[geometryOffsetsCounter] - geometryOffsets[geometryOffsetsCounter - 1]; geometryOffsetsCounter++; const points: Point[] = new Array(numPoints); if (nonOffset) { for (let j = 0; j < numPoints; j++) { const x = vertexBuffer[vertexBufferOffset++]; const y = vertexBuffer[vertexBufferOffset++]; points[j] = new Point(x, y); } } else { for (let j = 0; j < numPoints; j++) { const offset = vertexOffsets[vertexOffsetsOffset++] * 2; const x = vertexBuffer[offset]; const y = vertexBuffer[offset + 1]; points[j] = new Point(x, y); } } geometries[geometryCounter++] = points.map((point) => [point]); // MULTIPOINT must increment offset counters like POINT does partOffsetCounter += numPoints; ringOffsetsCounter += numPoints; } break; case GEOMETRY_TYPE.LINESTRING: { let numVertices: number; if (containsPolygon) { numVertices = ringOffsets[ringOffsetsCounter] - ringOffsets[ringOffsetsCounter - 1]; ringOffsetsCounter++; } else { numVertices = partOffsets[partOffsetCounter] - partOffsets[partOffsetCounter - 1]; } partOffsetCounter++; let vertices: Point[]; if (nonOffset) { vertices = getLineStringOrRing(vertexBuffer, vertexBufferOffset, numVertices, false); vertexBufferOffset += numVertices * 2; } else { vertices = decodeDictionaryEncodedLineStringOrRing( geometryVector.vertexBufferType, vertexBuffer, vertexOffsets, vertexOffsetsOffset, numVertices, false, mortonSettings, ); vertexOffsetsOffset += numVertices; } geometries[geometryCounter++] = [vertices]; if (geometryOffsets) geometryOffsetsCounter++; } break; case GEOMETRY_TYPE.POLYGON: { const numRings = partOffsets[partOffsetCounter] - partOffsets[partOffsetCounter - 1]; partOffsetCounter++; const rings: CoordinatesArray = new Array(numRings - 1); let shell: Point[]; let numVertices = ringOffsets[ringOffsetsCounter] - ringOffsets[ringOffsetsCounter - 1]; ringOffsetsCounter++; if (nonOffset) { shell = getLineStringOrRing(vertexBuffer, vertexBufferOffset, numVertices, true); vertexBufferOffset += numVertices * 2; for (let j = 0; j < rings.length; j++) { numVertices = ringOffsets[ringOffsetsCounter] - ringOffsets[ringOffsetsCounter - 1]; ringOffsetsCounter++; rings[j] = getLineStringOrRing(vertexBuffer, vertexBufferOffset, numVertices, true); vertexBufferOffset += numVertices * 2; } } else { shell = decodeDictionaryEncodedLineStringOrRing( geometryVector.vertexBufferType, vertexBuffer, vertexOffsets, vertexOffsetsOffset, numVertices, true, mortonSettings, ); vertexOffsetsOffset += numVertices; for (let j = 0; j < rings.length; j++) { numVertices = ringOffsets[ringOffsetsCounter] - ringOffsets[ringOffsetsCounter - 1]; ringOffsetsCounter++; rings[j] = decodeDictionaryEncodedLineStringOrRing( geometryVector.vertexBufferType, vertexBuffer, vertexOffsets, vertexOffsetsOffset, numVertices, true, mortonSettings, ); vertexOffsetsOffset += numVertices; } } geometries[geometryCounter++] = [shell].concat(rings); if (geometryOffsets) geometryOffsetsCounter++; } break; case GEOMETRY_TYPE.MULTILINESTRING: { const numLineStrings = geometryOffsets[geometryOffsetsCounter] - geometryOffsets[geometryOffsetsCounter - 1]; geometryOffsetsCounter++; const lineStrings: CoordinatesArray = new Array(numLineStrings); for (let j = 0; j < numLineStrings; j++) { let numVertices: number; if (containsPolygon) { numVertices = ringOffsets[ringOffsetsCounter] - ringOffsets[ringOffsetsCounter - 1]; ringOffsetsCounter++; } else { numVertices = partOffsets[partOffsetCounter] - partOffsets[partOffsetCounter - 1]; } partOffsetCounter++; if (nonOffset) { lineStrings[j] = getLineStringOrRing(vertexBuffer, vertexBufferOffset, numVertices, false); vertexBufferOffset += numVertices * 2; } else { const vertices = decodeDictionaryEncodedLineStringOrRing( geometryVector.vertexBufferType, vertexBuffer, vertexOffsets, vertexOffsetsOffset, numVertices, false, mortonSettings, ); lineStrings[j] = vertices; vertexOffsetsOffset += numVertices; } } geometries[geometryCounter++] = lineStrings; } break; case GEOMETRY_TYPE.MULTIPOLYGON: { const numPolygons = geometryOffsets[geometryOffsetsCounter] - geometryOffsets[geometryOffsetsCounter - 1]; geometryOffsetsCounter++; const polygons: CoordinatesArray[] = new Array(numPolygons); for (let j = 0; j < numPolygons; j++) { const numRings = partOffsets[partOffsetCounter] - partOffsets[partOffsetCounter - 1]; partOffsetCounter++; let shell: Point[]; const rings: CoordinatesArray = new Array(numRings - 1); const numVertices = ringOffsets[ringOffsetsCounter] - ringOffsets[ringOffsetsCounter - 1]; ringOffsetsCounter++; if (nonOffset) { shell = getLineStringOrRing(vertexBuffer, vertexBufferOffset, numVertices, true); vertexBufferOffset += numVertices * 2; } else { shell = decodeDictionaryEncodedLineStringOrRing( geometryVector.vertexBufferType, vertexBuffer, vertexOffsets, vertexOffsetsOffset, numVertices, true, mortonSettings, ); vertexOffsetsOffset += numVertices; } for (let k = 0; k < rings.length; k++) { const numRingVertices = ringOffsets[ringOffsetsCounter] - ringOffsets[ringOffsetsCounter - 1]; ringOffsetsCounter++; if (nonOffset) { rings[k] = getLineStringOrRing(vertexBuffer, vertexBufferOffset, numRingVertices, true); vertexBufferOffset += numRingVertices * 2; } else { rings[k] = decodeDictionaryEncodedLineStringOrRing( geometryVector.vertexBufferType, vertexBuffer, vertexOffsets, vertexOffsetsOffset, numRingVertices, true, mortonSettings, ); vertexOffsetsOffset += numRingVertices; } } polygons[j] = [shell].concat(rings); } geometries[geometryCounter++] = polygons.flat(); } break; default: throw new Error("The specified geometry type is currently not supported."); } } return geometries; } function decodeDictionaryEncodedLineStringOrRing( vertexBufferType: VertexBufferType, vertexBuffer: Int32Array | Uint32Array, vertexOffsets: Uint32Array, vertexOffset: number, numVertices: number, closeLineString: boolean, mortonSettings: MortonSettings, ): Point[] { if (vertexBufferType === VertexBufferType.MORTON) { return decodeMortonDictionaryEncodedLineString( vertexBuffer, vertexOffsets, vertexOffset, numVertices, closeLineString, mortonSettings, ); } else { return decodeDictionaryEncodedLineString( vertexBuffer, vertexOffsets, vertexOffset, numVertices, closeLineString, ); } } function getLineStringOrRing( vertexBuffer: Int32Array | Uint32Array, startIndex: number, numVertices: number, closeLineString: boolean, ): Point[] { const vertices: Point[] = new Array(closeLineString ? numVertices + 1 : numVertices); for (let i = 0; i < numVertices * 2; i += 2) { const x = vertexBuffer[startIndex + i]; const y = vertexBuffer[startIndex + i + 1]; vertices[i / 2] = new Point(x, y); } if (closeLineString) { vertices[vertices.length - 1] = vertices[0]; } return vertices; } function decodeDictionaryEncodedLineString( vertexBuffer: Int32Array | Uint32Array, vertexOffsets: Uint32Array, vertexOffset: number, numVertices: number, closeLineString: boolean, ): Point[] { const vertices: Point[] = new Array(closeLineString ? numVertices + 1 : numVertices); for (let i = 0; i < numVertices * 2; i += 2) { const offset = vertexOffsets[vertexOffset + i / 2] * 2; const x = vertexBuffer[offset]; const y = vertexBuffer[offset + 1]; vertices[i / 2] = new Point(x, y); } if (closeLineString) { vertices[vertices.length - 1] = vertices[0]; } return vertices; } function decodeMortonDictionaryEncodedLineString( vertexBuffer: Int32Array | Uint32Array, vertexOffsets: Uint32Array, vertexOffset: number, numVertices: number, closeLineString: boolean, mortonSettings: MortonSettings, ): Point[] { const vertices: Point[] = new Array(closeLineString ? numVertices + 1 : numVertices); for (let i = 0; i < numVertices; i++) { const offset = vertexOffsets[vertexOffset + i]; const mortonEncodedVertex = vertexBuffer[offset]; const vertex = decodeZOrderCurve(mortonEncodedVertex, mortonSettings.numBits, mortonSettings.coordinateShift); vertices[i] = new Point(vertex.x, vertex.y); } if (closeLineString) { vertices[vertices.length - 1] = vertices[0]; } return vertices; } ================================================ FILE: ts/src/vector/geometry/gpuVector.ts ================================================ import Point from "@mapbox/point-geometry"; import { GEOMETRY_TYPE } from "./geometryType"; import type { CoordinatesArray } from "./geometryVector"; import type { TopologyVector } from "./topologyVector"; export abstract class GpuVector implements Iterable { protected constructor( private readonly _triangleOffsets: Uint32Array, private readonly _indexBuffer: Uint32Array, private readonly _vertexBuffer: Int32Array | Uint32Array, private readonly _topologyVector?: TopologyVector, ) {} abstract geometryType(index: number): number; abstract get numGeometries(): number; abstract containsSingleGeometryType(): boolean; get triangleOffsets(): Uint32Array { return this._triangleOffsets; } get indexBuffer(): Uint32Array { return this._indexBuffer; } get vertexBuffer(): Int32Array | Uint32Array { return this._vertexBuffer; } get topologyVector(): TopologyVector | undefined { return this._topologyVector; } /** * Returns geometries as coordinate arrays by extracting polygon outlines from topology. * The vertexBuffer contains the outline vertices, separate from the tessellated triangles. */ getGeometries(): CoordinatesArray[] { if (!this._topologyVector) { throw new Error("Cannot convert GpuVector to coordinates without topology information"); } const geometries: CoordinatesArray[] = new Array(this.numGeometries); const topology = this._topologyVector; const partOffsets = topology.partOffsets; const ringOffsets = topology.ringOffsets; const geometryOffsets = topology.geometryOffsets; // Use counters to track position in offset arrays (like Java implementation) let vertexBufferOffset = 0; let partOffsetCounter = 1; let ringOffsetsCounter = 1; let geometryOffsetsCounter = 1; for (let i = 0; i < this.numGeometries; i++) { const geometryType = this.geometryType(i); switch (geometryType) { case GEOMETRY_TYPE.POLYGON: { // Get number of rings for this polygon const numRings = partOffsets[partOffsetCounter] - partOffsets[partOffsetCounter - 1]; partOffsetCounter++; const rings: Point[][] = []; for (let j = 0; j < numRings; j++) { // Get number of vertices in this ring const numVertices = ringOffsets[ringOffsetsCounter] - ringOffsets[ringOffsetsCounter - 1]; ringOffsetsCounter++; const ring: Point[] = []; for (let k = 0; k < numVertices; k++) { const x = this._vertexBuffer[vertexBufferOffset++]; const y = this._vertexBuffer[vertexBufferOffset++]; ring.push(new Point(x, y)); } // Close the ring by duplicating the first vertex (MVT format requirement) if (ring.length > 0) { ring.push(ring[0]); } rings.push(ring); } geometries[i] = rings; if (geometryOffsets) geometryOffsetsCounter++; } break; case GEOMETRY_TYPE.MULTIPOLYGON: { // Get number of polygons in this multipolygon const numPolygons = geometryOffsets[geometryOffsetsCounter] - geometryOffsets[geometryOffsetsCounter - 1]; geometryOffsetsCounter++; const allRings: Point[][] = []; for (let p = 0; p < numPolygons; p++) { // Get number of rings in this polygon const numRings = partOffsets[partOffsetCounter] - partOffsets[partOffsetCounter - 1]; partOffsetCounter++; for (let j = 0; j < numRings; j++) { // Get number of vertices in this ring const numVertices = ringOffsets[ringOffsetsCounter] - ringOffsets[ringOffsetsCounter - 1]; ringOffsetsCounter++; const ring: Point[] = []; for (let k = 0; k < numVertices; k++) { const x = this._vertexBuffer[vertexBufferOffset++]; const y = this._vertexBuffer[vertexBufferOffset++]; ring.push(new Point(x, y)); } // Close the ring by duplicating the first vertex (MVT format requirement) if (ring.length > 0) { ring.push(ring[0]); } allRings.push(ring); } } geometries[i] = allRings; } break; } } return geometries; } [Symbol.iterator](): Iterator { /*for(let i = 1; i < this.triangleOffsets.length; i++) { const numTriangles = this.triangleOffsets[i] - this.triangleOffsets[i-1]; const startIndex = this.triangleOffsets[i-1] * 3; const endIndex = this.triangleOffsets[i] * 3; } while (index < this.numGeometries) { yield geometries[index++]; }*/ //throw new Error("Iterator on a GpuVector is not implemented yet."); return null; } } ================================================ FILE: ts/src/vector/geometry/topologyVector.ts ================================================ export type TopologyVector = { readonly geometryOffsets?: Uint32Array; readonly partOffsets?: Uint32Array; readonly ringOffsets?: Uint32Array; }; ================================================ FILE: ts/src/vector/geometry/vertexBufferType.ts ================================================ export enum VertexBufferType { MORTON = 0, VEC_2 = 1, VEC_3 = 2, } ================================================ FILE: ts/src/vector/geometry/zOrderCurve.spec.ts ================================================ import { describe, it, expect } from "vitest"; import { decodeZOrderCurve } from "./zOrderCurve"; import { encodeZOrderCurve } from "../../encoding/zOrderCurveEncoder"; describe("zOrderCurve", () => { it("should encode and decode z-order curve", () => { const x = 3358; const y = 4130; const numBits = 13; const coordinateShift = 0; const encoded = encodeZOrderCurve(x, y, numBits, coordinateShift); expect(encoded).toBe(38865244); const decoded = decodeZOrderCurve(encoded, numBits, coordinateShift); expect(decoded).toEqual({ x, y }); }); it("should handle coordinate shift", () => { const x = -50; const y = 30; const numBits = 8; const coordinateShift = 100; const encoded = encodeZOrderCurve(x, y, numBits, coordinateShift); const decoded = decodeZOrderCurve(encoded, numBits, coordinateShift); expect(decoded).toEqual({ x, y }); }); }); ================================================ FILE: ts/src/vector/geometry/zOrderCurve.ts ================================================ export function decodeZOrderCurve( mortonCode: number, numBits: number, coordinateShift: number, ): { x: number; y: number } { const x = decodeMorton(mortonCode, numBits) - coordinateShift; const y = decodeMorton(mortonCode >> 1, numBits) - coordinateShift; return { x, y }; } function decodeMorton(code: number, numBits: number): number { let coordinate = 0; for (let i = 0; i < numBits; i++) { coordinate |= (code & (1 << (2 * i))) >> i; } return coordinate; } ================================================ FILE: ts/src/vector/idVector.ts ================================================ import type { Int32FlatVector } from "./flat/int32FlatVector"; import type { Int64FlatVector } from "./flat/int64FlatVector"; import type { Int32ConstVector } from "./constant/int32ConstVector"; import type { Int64ConstVector } from "./constant/int64ConstVector"; import type { Int32SequenceVector } from "./sequence/int32SequenceVector"; import type { Int64SequenceVector } from "./sequence/int64SequenceVector"; import type { DoubleFlatVector } from "./flat/doubleFlatVector"; export type IdVector = | Int32FlatVector | Int64FlatVector | DoubleFlatVector | Int32SequenceVector | Int64SequenceVector | Int32ConstVector | Int64ConstVector; ================================================ FILE: ts/src/vector/sequence/int32SequenceVector.ts ================================================ import { SequenceVector } from "./sequenceVector"; export class Int32SequenceVector extends SequenceVector { public constructor(name: string, baseValue: number, delta: number, size: number) { super(name, Int32Array.of(baseValue), delta, size); } protected getValueFromBuffer(index: number): number { return this.dataBuffer[0] + index * this.delta; } } ================================================ FILE: ts/src/vector/sequence/int64SequenceVector.spec.ts ================================================ import { describe, expect, it } from "vitest"; import { Int64SequenceVector } from "./int64SequenceVector"; describe("Int64SequenceVector", () => { it("should generate bigint sequence values", () => { const vec = new Int64SequenceVector("test", 10n, 5n, 5); // 10n, 15n, 20n, 25n, 30n expect(vec.getValue(0)).toBe(10n); expect(vec.getValue(2)).toBe(20n); expect(vec.getValue(4)).toBe(30n); }); }); ================================================ FILE: ts/src/vector/sequence/int64SequenceVector.ts ================================================ import { SequenceVector } from "./sequenceVector"; export class Int64SequenceVector extends SequenceVector { public constructor(name: string, baseValue: bigint, delta: bigint, size: number) { super(name, BigInt64Array.of(baseValue), delta, size); } protected getValueFromBuffer(index: number): bigint { return this.dataBuffer[0] + BigInt(index) * this.delta; } } ================================================ FILE: ts/src/vector/sequence/sequenceVector.ts ================================================ import Vector from "../vector"; export abstract class SequenceVector extends Vector { protected readonly delta: K; protected constructor(name: string, baseValueBuffer: T, delta: K, size: number) { super(name, baseValueBuffer, size); this.delta = delta; } } ================================================ FILE: ts/src/vector/variableSizeVector.ts ================================================ import type BitVector from "./flat/bitVector"; import Vector from "./vector"; export abstract class VariableSizeVector extends Vector { protected constructor( name: string, protected offsetBuffer: Uint32Array, dataBuffer: T, sizeOrNullabilityBuffer: number | BitVector, ) { super(name, dataBuffer, sizeOrNullabilityBuffer); } } ================================================ FILE: ts/src/vector/vector.ts ================================================ import type BitVector from "./flat/bitVector"; export default abstract class Vector { protected nullabilityBuffer: BitVector | null; protected _size: number; constructor( private readonly _name: string, protected readonly dataBuffer: T, sizeOrNullabilityBuffer: number | BitVector, ) { if (typeof sizeOrNullabilityBuffer === "number") { this._size = sizeOrNullabilityBuffer; } else { this.nullabilityBuffer = sizeOrNullabilityBuffer; this._size = sizeOrNullabilityBuffer.size(); } } getValue(index: number): K | null { return this.nullabilityBuffer && !this.nullabilityBuffer.get(index) ? null : this.getValueFromBuffer(index); } has(index: number): boolean { return this.nullabilityBuffer?.get(index) || !this.nullabilityBuffer; } get name(): string { return this._name; } get size(): number { return this._size; } protected abstract getValueFromBuffer(index: number): K; } ================================================ FILE: ts/src/vector/vectorType.ts ================================================ export enum VectorType { FLAT = 0, CONST = 1, SEQUENCE = 2, DICTIONARY = 3, FSST_DICTIONARY = 4, } ================================================ FILE: ts/tsconfig.json ================================================ { "compilerOptions": { "target": "ES2020", "outDir": "./dist", "resolveJsonModule": true, "module": "es2022", "declaration": true, "sourceMap": true, "inlineSources": true, "moduleResolution": "bundler", "esModuleInterop": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "types": [ "node" ] }, "include": [ "src/**/*.ts" ], "exclude": [ "node_modules", "**/*.spec.ts", ] } ================================================ FILE: ts/tsconfig.lint.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "noEmit": true }, "include": [ "src/**/*.ts" ], "exclude": [ "node_modules" ] }