├── .dockerignore ├── .github └── workflows │ └── docker.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── NOTICE ├── README.md ├── docker ├── debug.dockerfile ├── strip-docker-image-export └── tyler.dockerfile ├── docs ├── design_document.md └── img │ ├── ecef.jpg │ ├── ecef.svg │ └── quadtree.svg ├── resources ├── data │ ├── 3dbag_feature_x71.city.jsonl │ ├── 3dbag_x00.city.json │ └── README.md ├── geof │ ├── createGLB.json │ ├── fix_attributes.py │ ├── process_feature.json │ └── remove_empty_leafs.py └── python │ ├── adjust_geometric_error.py │ ├── convert_cityjsonfeatures.py │ ├── print_quadtree.py │ └── split_to_features.py ├── src ├── cli.rs ├── formats.rs ├── main.rs ├── parser.rs ├── proj.rs └── spatial_structs.rs └── tyler.png /.dockerignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .run 3 | .vs 4 | .git 5 | target 6 | target-windows -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: 4 | - published 5 | 6 | name: Docker build 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | with: 13 | submodules: true 14 | - uses: elgohr/Publish-Docker-Github-Action@v5 15 | env: 16 | VERSION: ${{ github.ref }} 17 | with: 18 | name: "3dgi/tyler" 19 | username: ${{ secrets.DOCKER_USERNAME }} 20 | password: ${{ secrets.DOCKER_PASSWORD }} 21 | dockerfile: docker/tyler.dockerfile 22 | tag_semver: true 23 | buildargs: VERSION 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build 2 | /target 3 | /target-windows 4 | 5 | # IDE 6 | .idea 7 | .run 8 | .vs 9 | /resources/data/features_3dbag_5909/ 10 | 11 | # misc 12 | /tmp 13 | 14 | # Debug data 15 | *.tsv 16 | *.bincode 17 | *.log 18 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "proj"] 2 | path = proj 3 | url = https://github.com/balazsdukai/proj.git 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## tyler 0.3.11 (2024-07-25) 4 | 5 | ### Fixed 6 | 7 | - If there is only a single tile, the root contains the content and the tileset geometricError is 8 | 0.00. ([#45](https://github.com/3DGI/tyler/issues/45)) 9 | 10 | ## tyler 0.3.10 (2024-03-08) 11 | 12 | ### Changed 13 | 14 | - Update geoflow-bundle docker image 15 | 16 | ### Added 17 | 18 | - The `smooth-normals` and `simplification-max-error` options 19 | 20 | ## tyler 0.3.9 (2023-12-09) 21 | 22 | ### Fixed 23 | 24 | - Parsing CityObjects without a `geometry` member 25 | - Median vertex count from a single CityObject 26 | 27 | ## tyler 0.3.8 (2023-09-18) 28 | 29 | ### Changed 30 | 31 | - Remove debug info from release build 32 | - Geometric error above the leafs defaults to 12 33 | - Update geoflow-bundle docker image 34 | 35 | ## tyler 0.3.7 (2023-08-21) 36 | 37 | ### Fixed 38 | 39 | - Geometric error calculation for the parents of the deepest leaves, where the leaf has an edge length of the grid 40 | cellsize. 41 | - Replace maxz in leaf content if minz is greater than maxz. 42 | - Tile bounding volume when tile is empty. 43 | - Fixed the hardcoded timeout. 44 | 45 | ### Added 46 | 47 | - Set content bounding volume from tile bounding volume with the `--3dtiles-content-bv-from-tile` option. Required when 48 | the tile content is clipped to the tile's extent, for example for terrain. 49 | - Write quadtree content bounding box to .tsv with `--grid-export`. 50 | - Log all arguments in debug. 51 | - Split an explicit tileset to external tilesets if the tree is deep. 52 | - GitHub Action for publishing to DockerHub to `3dgi/tyler` ([#40](https://github.com/3DGI/tyler/pull/40)) 53 | 54 | ### Changed 55 | 56 | - Debug data, incl. `--grid-export`, is written to the `debug` directory within the `--output` directory. 57 | - Remove logging from geof. Speeds up the conversion and fixes the extreme memory consumption when geof emits a large 58 | amount of messages. 59 | - Use BufWriter for writing the input paths. 60 | - Implement parallel computation for the extent, where the direct subdirectories of `--features` are visited in 61 | parallel (but their contents are processed sequentially). 62 | - Floats in the 3D Tiles tileset.json are written with 2 decimals 63 | - Implement parallel indexing of the features, where the direct subdirectories of `--features` are visited in parallel ( 64 | but their contents are processed sequentially). 65 | - The grid is centered at the computed extent, instead of matching their origin. 66 | - Rename `tiles` directory to `t` to save space in the tileset.json 67 | - The tile content bounding volume, `content.boundingVolume`, is not added to the tile content anymore. You need to 68 | enable this option if you want to include the content bounding volumes. Enable it with `--3dtiles-content-add-bv`. 69 | - The grid's cell size is adjusted so that it is possible to construct a tightly fit square of 4^n cells. The final cell 70 | size will be larger than what is set with `--grid-cellsize`. 71 | 72 | ## tyler 0.3.6 (2023-07-17) 73 | 74 | ### Changed 75 | 76 | - The `--grid-export` switch does not export the feature centroids anymore. Use the `--grid-export-features` if you want 77 | to export the feature centroids together with the grid cells. 78 | - Write both pruned and unpruned tilesets. Unpruned tiles are only written in debug mode. 79 | - Reduced the logging in debug mode. 80 | 81 | ### Fixed 82 | 83 | - Invalid subtree for implicit tiling, in case of very large areas (eg. the Netherlands). 84 | 85 | ### Added 86 | 87 | - Write the `world`, `quadtree` and `tiles_failed` instances to bincode when running in debug mode. The instances can be 88 | loaded for debugging with the `--debug-load-data`, in which case tyler will load the instance data instead of 89 | generating it. 90 | - Describe how to generate debug data. 91 | 92 | ## tyler 0.3.5 (2023-06-28) 93 | 94 | ### Added 95 | 96 | - Option to only generate and write the 3D Tiles tileset, without running the glTF export. Enable 97 | with `--3dtiles-tileset-only`. 98 | - Timeout `--timeout` in seconds for the converter subprocesses. If speficied, tyler will kill the subprocess after the 99 | provided seconds, otherwise it will wait for the process to finish. 100 | 101 | ### Fixed 102 | 103 | - geoflow version reporting 104 | 105 | ## tyler 0.3.4 (2023-06-22) 106 | 107 | ### Fixed 108 | 109 | - geoflow-bundle version in the docker image 110 | 111 | ## tyler 0.3.3 (2023-06-19) 112 | 113 | ### Fixed 114 | 115 | - Infinite loop in the glb conversion in geoflow-bundle, in a rare case during the mesh simplification. 116 | - Infinite loop in case there is not a single CityJSONFeature file in the directory tree. 117 | 118 | ### Changed 119 | 120 | - tyler and geoflow versions are reported as info 121 | 122 | ## tyler 0.3.2 (2023-05-05) 123 | 124 | ### Fixed 125 | 126 | - *proj* related fixes for running Tyler under Windows 127 | 128 | ## tyler 0.3.1 (2023-04-06) 129 | 130 | ### Changed 131 | 132 | - Add the *proj* crate as a submodule, because the proj-sys build script need to be changed so that proj-sys can be 133 | built with MYSYS2 on Windows (see https://github.com/georust/proj/pull/156). 134 | - Warning instead of error when the gltf export in the subprocess fails (fixes #36). 135 | - The geoflow flowchart directory can be set at runtime with the `TYLER_RESOURCES_DIR` environment variable. By default, 136 | the directory is set to the `CARGO_MANIFEST_DIR` at compile time. 137 | - Features are assigned to tiles based on their bounding box, instead of only their vertices (fixes #28). 138 | - Update the geoflow docker image. 139 | - Improve documentation. 140 | 141 | ## tyler 0.3.0 (2023-03-17) 142 | 143 | First public release of Tyler. 144 | With this version Tyler can generate [3D Tiles v1.1](https://docs.ogc.org/cs/22-025r4/22-025r4.html) 145 | from [CityJSONFeatures](https://www.cityjson.org/specs/1.1.3/#text-sequences-and-streaming-with-cityjsonfeature) that 146 | are stored individually in files. 147 | 148 | Details of the 3D Tiles output: 149 | 150 | - The tileset content if binary glTF (.glb). 151 | - The glTF assets contain feature metadata (per CityObject), using 152 | the [EXT_mesh_features](https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_mesh_features) 153 | and [EXT_structural_metadata](https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_structural_metadata) 154 | extensions. 155 | - The features are colored to default values, and the colors can by set per CityObject type. 156 | - The glTF files are compressed, using 157 | the [KHR_mesh_quantization](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_mesh_quantization) 158 | and [EXT_meshopt_compression](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Vendor/EXT_meshopt_compression) 159 | extensions. 160 | - Implicit tiling is supported (optional). 161 | 162 | The current version depends on the [geoflow-bundle](https://github.com/geoflow3d/geoflow-bundle) for converting 163 | CityJSONFeatures to glTF. 164 | Therefore, we strongly recommend to use the provided docker image for running Tyler. 165 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "aho-corasick" 13 | version = "0.7.20" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" 16 | dependencies = [ 17 | "memchr", 18 | ] 19 | 20 | [[package]] 21 | name = "atty" 22 | version = "0.2.14" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 25 | dependencies = [ 26 | "hermit-abi 0.1.19", 27 | "libc", 28 | "winapi", 29 | ] 30 | 31 | [[package]] 32 | name = "autocfg" 33 | version = "1.1.0" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 36 | 37 | [[package]] 38 | name = "bincode" 39 | version = "1.3.3" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 42 | dependencies = [ 43 | "serde", 44 | ] 45 | 46 | [[package]] 47 | name = "bindgen" 48 | version = "0.60.1" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" 51 | dependencies = [ 52 | "bitflags", 53 | "cexpr", 54 | "clang-sys", 55 | "clap 3.2.23", 56 | "env_logger 0.9.3", 57 | "lazy_static", 58 | "lazycell", 59 | "log", 60 | "peeking_take_while", 61 | "proc-macro2", 62 | "quote", 63 | "regex", 64 | "rustc-hash", 65 | "shlex", 66 | "which", 67 | ] 68 | 69 | [[package]] 70 | name = "bitflags" 71 | version = "1.3.2" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 74 | 75 | [[package]] 76 | name = "bitvec" 77 | version = "1.0.1" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" 80 | dependencies = [ 81 | "funty", 82 | "radium", 83 | "tap", 84 | "wyz", 85 | ] 86 | 87 | [[package]] 88 | name = "cc" 89 | version = "1.0.78" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" 92 | 93 | [[package]] 94 | name = "cexpr" 95 | version = "0.6.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 98 | dependencies = [ 99 | "nom", 100 | ] 101 | 102 | [[package]] 103 | name = "cfg-if" 104 | version = "1.0.0" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 107 | 108 | [[package]] 109 | name = "clang-sys" 110 | version = "1.4.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" 113 | dependencies = [ 114 | "glob", 115 | "libc", 116 | "libloading", 117 | ] 118 | 119 | [[package]] 120 | name = "clap" 121 | version = "3.2.23" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" 124 | dependencies = [ 125 | "atty", 126 | "bitflags", 127 | "clap_lex 0.2.4", 128 | "indexmap", 129 | "strsim", 130 | "termcolor", 131 | "textwrap", 132 | ] 133 | 134 | [[package]] 135 | name = "clap" 136 | version = "4.0.32" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" 139 | dependencies = [ 140 | "bitflags", 141 | "clap_derive", 142 | "clap_lex 0.3.0", 143 | "is-terminal", 144 | "once_cell", 145 | "strsim", 146 | "termcolor", 147 | ] 148 | 149 | [[package]] 150 | name = "clap_derive" 151 | version = "4.0.21" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" 154 | dependencies = [ 155 | "heck", 156 | "proc-macro-error", 157 | "proc-macro2", 158 | "quote", 159 | "syn", 160 | ] 161 | 162 | [[package]] 163 | name = "clap_lex" 164 | version = "0.2.4" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 167 | dependencies = [ 168 | "os_str_bytes", 169 | ] 170 | 171 | [[package]] 172 | name = "clap_lex" 173 | version = "0.3.0" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" 176 | dependencies = [ 177 | "os_str_bytes", 178 | ] 179 | 180 | [[package]] 181 | name = "cmake" 182 | version = "0.1.49" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "db34956e100b30725f2eb215f90d4871051239535632f84fea3bc92722c66b7c" 185 | dependencies = [ 186 | "cc", 187 | ] 188 | 189 | [[package]] 190 | name = "crc32fast" 191 | version = "1.3.2" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 194 | dependencies = [ 195 | "cfg-if", 196 | ] 197 | 198 | [[package]] 199 | name = "crossbeam-channel" 200 | version = "0.5.6" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" 203 | dependencies = [ 204 | "cfg-if", 205 | "crossbeam-utils", 206 | ] 207 | 208 | [[package]] 209 | name = "crossbeam-deque" 210 | version = "0.8.2" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" 213 | dependencies = [ 214 | "cfg-if", 215 | "crossbeam-epoch", 216 | "crossbeam-utils", 217 | ] 218 | 219 | [[package]] 220 | name = "crossbeam-epoch" 221 | version = "0.9.13" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" 224 | dependencies = [ 225 | "autocfg", 226 | "cfg-if", 227 | "crossbeam-utils", 228 | "memoffset", 229 | "scopeguard", 230 | ] 231 | 232 | [[package]] 233 | name = "crossbeam-utils" 234 | version = "0.8.14" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" 237 | dependencies = [ 238 | "cfg-if", 239 | ] 240 | 241 | [[package]] 242 | name = "either" 243 | version = "1.8.0" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" 246 | 247 | [[package]] 248 | name = "env_logger" 249 | version = "0.9.3" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" 252 | dependencies = [ 253 | "atty", 254 | "humantime", 255 | "log", 256 | "regex", 257 | "termcolor", 258 | ] 259 | 260 | [[package]] 261 | name = "env_logger" 262 | version = "0.10.0" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" 265 | dependencies = [ 266 | "humantime", 267 | "is-terminal", 268 | "log", 269 | "regex", 270 | "termcolor", 271 | ] 272 | 273 | [[package]] 274 | name = "errno" 275 | version = "0.2.8" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" 278 | dependencies = [ 279 | "errno-dragonfly", 280 | "libc", 281 | "winapi", 282 | ] 283 | 284 | [[package]] 285 | name = "errno-dragonfly" 286 | version = "0.1.2" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 289 | dependencies = [ 290 | "cc", 291 | "libc", 292 | ] 293 | 294 | [[package]] 295 | name = "filetime" 296 | version = "0.2.19" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" 299 | dependencies = [ 300 | "cfg-if", 301 | "libc", 302 | "redox_syscall", 303 | "windows-sys", 304 | ] 305 | 306 | [[package]] 307 | name = "flate2" 308 | version = "1.0.25" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" 311 | dependencies = [ 312 | "crc32fast", 313 | "miniz_oxide", 314 | ] 315 | 316 | [[package]] 317 | name = "funty" 318 | version = "2.0.0" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" 321 | 322 | [[package]] 323 | name = "glob" 324 | version = "0.3.0" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 327 | 328 | [[package]] 329 | name = "hashbrown" 330 | version = "0.12.3" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 333 | 334 | [[package]] 335 | name = "heck" 336 | version = "0.4.0" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 339 | 340 | [[package]] 341 | name = "hermit-abi" 342 | version = "0.1.19" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 345 | dependencies = [ 346 | "libc", 347 | ] 348 | 349 | [[package]] 350 | name = "hermit-abi" 351 | version = "0.2.6" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 354 | dependencies = [ 355 | "libc", 356 | ] 357 | 358 | [[package]] 359 | name = "humantime" 360 | version = "2.1.0" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 363 | 364 | [[package]] 365 | name = "indexmap" 366 | version = "1.9.3" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 369 | dependencies = [ 370 | "autocfg", 371 | "hashbrown", 372 | ] 373 | 374 | [[package]] 375 | name = "io-lifetimes" 376 | version = "1.0.3" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" 379 | dependencies = [ 380 | "libc", 381 | "windows-sys", 382 | ] 383 | 384 | [[package]] 385 | name = "is-terminal" 386 | version = "0.4.2" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" 389 | dependencies = [ 390 | "hermit-abi 0.2.6", 391 | "io-lifetimes", 392 | "rustix", 393 | "windows-sys", 394 | ] 395 | 396 | [[package]] 397 | name = "itoa" 398 | version = "1.0.5" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" 401 | 402 | [[package]] 403 | name = "lazy_static" 404 | version = "1.4.0" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 407 | 408 | [[package]] 409 | name = "lazycell" 410 | version = "1.3.0" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 413 | 414 | [[package]] 415 | name = "libc" 416 | version = "0.2.147" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 419 | 420 | [[package]] 421 | name = "libloading" 422 | version = "0.7.4" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" 425 | dependencies = [ 426 | "cfg-if", 427 | "winapi", 428 | ] 429 | 430 | [[package]] 431 | name = "linux-raw-sys" 432 | version = "0.1.4" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" 435 | 436 | [[package]] 437 | name = "log" 438 | version = "0.4.17" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 441 | dependencies = [ 442 | "cfg-if", 443 | ] 444 | 445 | [[package]] 446 | name = "memchr" 447 | version = "2.5.0" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 450 | 451 | [[package]] 452 | name = "memoffset" 453 | version = "0.7.1" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" 456 | dependencies = [ 457 | "autocfg", 458 | ] 459 | 460 | [[package]] 461 | name = "minimal-lexical" 462 | version = "0.2.1" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 465 | 466 | [[package]] 467 | name = "miniz_oxide" 468 | version = "0.6.2" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" 471 | dependencies = [ 472 | "adler", 473 | ] 474 | 475 | [[package]] 476 | name = "morton-encoding" 477 | version = "2.0.1" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "f66c953d92a578cd98a4598021e3b473520d214665917eb51dba49dc227936c8" 480 | dependencies = [ 481 | "num", 482 | "num-traits", 483 | ] 484 | 485 | [[package]] 486 | name = "nom" 487 | version = "7.1.2" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" 490 | dependencies = [ 491 | "memchr", 492 | "minimal-lexical", 493 | ] 494 | 495 | [[package]] 496 | name = "num" 497 | version = "0.2.1" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" 500 | dependencies = [ 501 | "num-bigint", 502 | "num-complex", 503 | "num-integer", 504 | "num-iter", 505 | "num-rational", 506 | "num-traits", 507 | ] 508 | 509 | [[package]] 510 | name = "num-bigint" 511 | version = "0.2.6" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" 514 | dependencies = [ 515 | "autocfg", 516 | "num-integer", 517 | "num-traits", 518 | ] 519 | 520 | [[package]] 521 | name = "num-complex" 522 | version = "0.2.4" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" 525 | dependencies = [ 526 | "autocfg", 527 | "num-traits", 528 | ] 529 | 530 | [[package]] 531 | name = "num-integer" 532 | version = "0.1.45" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 535 | dependencies = [ 536 | "autocfg", 537 | "num-traits", 538 | ] 539 | 540 | [[package]] 541 | name = "num-iter" 542 | version = "0.1.43" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" 545 | dependencies = [ 546 | "autocfg", 547 | "num-integer", 548 | "num-traits", 549 | ] 550 | 551 | [[package]] 552 | name = "num-rational" 553 | version = "0.2.4" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" 556 | dependencies = [ 557 | "autocfg", 558 | "num-bigint", 559 | "num-integer", 560 | "num-traits", 561 | ] 562 | 563 | [[package]] 564 | name = "num-traits" 565 | version = "0.2.15" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 568 | dependencies = [ 569 | "autocfg", 570 | ] 571 | 572 | [[package]] 573 | name = "num_cpus" 574 | version = "1.15.0" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" 577 | dependencies = [ 578 | "hermit-abi 0.2.6", 579 | "libc", 580 | ] 581 | 582 | [[package]] 583 | name = "once_cell" 584 | version = "1.16.0" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" 587 | 588 | [[package]] 589 | name = "os_str_bytes" 590 | version = "6.4.1" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" 593 | 594 | [[package]] 595 | name = "peeking_take_while" 596 | version = "0.1.2" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 599 | 600 | [[package]] 601 | name = "pkg-config" 602 | version = "0.3.26" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" 605 | 606 | [[package]] 607 | name = "proc-macro-error" 608 | version = "1.0.4" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 611 | dependencies = [ 612 | "proc-macro-error-attr", 613 | "proc-macro2", 614 | "quote", 615 | "syn", 616 | "version_check", 617 | ] 618 | 619 | [[package]] 620 | name = "proc-macro-error-attr" 621 | version = "1.0.4" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 624 | dependencies = [ 625 | "proc-macro2", 626 | "quote", 627 | "version_check", 628 | ] 629 | 630 | [[package]] 631 | name = "proc-macro2" 632 | version = "1.0.49" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" 635 | dependencies = [ 636 | "unicode-ident", 637 | ] 638 | 639 | [[package]] 640 | name = "proj-sys" 641 | version = "0.23.1" 642 | dependencies = [ 643 | "bindgen", 644 | "cmake", 645 | "flate2", 646 | "pkg-config", 647 | "tar", 648 | ] 649 | 650 | [[package]] 651 | name = "quote" 652 | version = "1.0.23" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 655 | dependencies = [ 656 | "proc-macro2", 657 | ] 658 | 659 | [[package]] 660 | name = "radium" 661 | version = "0.7.0" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" 664 | 665 | [[package]] 666 | name = "rayon" 667 | version = "1.6.1" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" 670 | dependencies = [ 671 | "either", 672 | "rayon-core", 673 | ] 674 | 675 | [[package]] 676 | name = "rayon-core" 677 | version = "1.10.1" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" 680 | dependencies = [ 681 | "crossbeam-channel", 682 | "crossbeam-deque", 683 | "crossbeam-utils", 684 | "num_cpus", 685 | ] 686 | 687 | [[package]] 688 | name = "redox_syscall" 689 | version = "0.2.16" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 692 | dependencies = [ 693 | "bitflags", 694 | ] 695 | 696 | [[package]] 697 | name = "regex" 698 | version = "1.7.0" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" 701 | dependencies = [ 702 | "aho-corasick", 703 | "memchr", 704 | "regex-syntax", 705 | ] 706 | 707 | [[package]] 708 | name = "regex-syntax" 709 | version = "0.6.28" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 712 | 713 | [[package]] 714 | name = "rustc-hash" 715 | version = "1.1.0" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 718 | 719 | [[package]] 720 | name = "rustix" 721 | version = "0.36.5" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588" 724 | dependencies = [ 725 | "bitflags", 726 | "errno", 727 | "io-lifetimes", 728 | "libc", 729 | "linux-raw-sys", 730 | "windows-sys", 731 | ] 732 | 733 | [[package]] 734 | name = "ryu" 735 | version = "1.0.12" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" 738 | 739 | [[package]] 740 | name = "same-file" 741 | version = "1.0.6" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 744 | dependencies = [ 745 | "winapi-util", 746 | ] 747 | 748 | [[package]] 749 | name = "scopeguard" 750 | version = "1.1.0" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 753 | 754 | [[package]] 755 | name = "serde" 756 | version = "1.0.152" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" 759 | dependencies = [ 760 | "serde_derive", 761 | ] 762 | 763 | [[package]] 764 | name = "serde_derive" 765 | version = "1.0.152" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" 768 | dependencies = [ 769 | "proc-macro2", 770 | "quote", 771 | "syn", 772 | ] 773 | 774 | [[package]] 775 | name = "serde_json" 776 | version = "1.0.91" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" 779 | dependencies = [ 780 | "itoa", 781 | "ryu", 782 | "serde", 783 | ] 784 | 785 | [[package]] 786 | name = "serde_repr" 787 | version = "0.1.10" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" 790 | dependencies = [ 791 | "proc-macro2", 792 | "quote", 793 | "syn", 794 | ] 795 | 796 | [[package]] 797 | name = "shlex" 798 | version = "1.1.0" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" 801 | 802 | [[package]] 803 | name = "strsim" 804 | version = "0.10.0" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 807 | 808 | [[package]] 809 | name = "subprocess" 810 | version = "0.2.9" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "0c2e86926081dda636c546d8c5e641661049d7562a68f5488be4a1f7f66f6086" 813 | dependencies = [ 814 | "libc", 815 | "winapi", 816 | ] 817 | 818 | [[package]] 819 | name = "syn" 820 | version = "1.0.107" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 823 | dependencies = [ 824 | "proc-macro2", 825 | "quote", 826 | "unicode-ident", 827 | ] 828 | 829 | [[package]] 830 | name = "tap" 831 | version = "1.0.1" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" 834 | 835 | [[package]] 836 | name = "tar" 837 | version = "0.4.38" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" 840 | dependencies = [ 841 | "filetime", 842 | "libc", 843 | "xattr", 844 | ] 845 | 846 | [[package]] 847 | name = "termcolor" 848 | version = "1.1.3" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 851 | dependencies = [ 852 | "winapi-util", 853 | ] 854 | 855 | [[package]] 856 | name = "textwrap" 857 | version = "0.16.0" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" 860 | 861 | [[package]] 862 | name = "thiserror" 863 | version = "1.0.38" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" 866 | dependencies = [ 867 | "thiserror-impl", 868 | ] 869 | 870 | [[package]] 871 | name = "thiserror-impl" 872 | version = "1.0.38" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" 875 | dependencies = [ 876 | "proc-macro2", 877 | "quote", 878 | "syn", 879 | ] 880 | 881 | [[package]] 882 | name = "tyler" 883 | version = "0.3.11" 884 | dependencies = [ 885 | "bincode", 886 | "bitvec", 887 | "clap 4.0.32", 888 | "env_logger 0.10.0", 889 | "libc", 890 | "log", 891 | "morton-encoding", 892 | "num-traits", 893 | "proj-sys", 894 | "rayon", 895 | "serde", 896 | "serde_json", 897 | "serde_repr", 898 | "subprocess", 899 | "thiserror", 900 | "walkdir", 901 | ] 902 | 903 | [[package]] 904 | name = "unicode-ident" 905 | version = "1.0.6" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 908 | 909 | [[package]] 910 | name = "version_check" 911 | version = "0.9.4" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 914 | 915 | [[package]] 916 | name = "walkdir" 917 | version = "2.3.2" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" 920 | dependencies = [ 921 | "same-file", 922 | "winapi", 923 | "winapi-util", 924 | ] 925 | 926 | [[package]] 927 | name = "which" 928 | version = "4.3.0" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" 931 | dependencies = [ 932 | "either", 933 | "libc", 934 | "once_cell", 935 | ] 936 | 937 | [[package]] 938 | name = "winapi" 939 | version = "0.3.9" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 942 | dependencies = [ 943 | "winapi-i686-pc-windows-gnu", 944 | "winapi-x86_64-pc-windows-gnu", 945 | ] 946 | 947 | [[package]] 948 | name = "winapi-i686-pc-windows-gnu" 949 | version = "0.4.0" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 952 | 953 | [[package]] 954 | name = "winapi-util" 955 | version = "0.1.5" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 958 | dependencies = [ 959 | "winapi", 960 | ] 961 | 962 | [[package]] 963 | name = "winapi-x86_64-pc-windows-gnu" 964 | version = "0.4.0" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 967 | 968 | [[package]] 969 | name = "windows-sys" 970 | version = "0.42.0" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 973 | dependencies = [ 974 | "windows_aarch64_gnullvm", 975 | "windows_aarch64_msvc", 976 | "windows_i686_gnu", 977 | "windows_i686_msvc", 978 | "windows_x86_64_gnu", 979 | "windows_x86_64_gnullvm", 980 | "windows_x86_64_msvc", 981 | ] 982 | 983 | [[package]] 984 | name = "windows_aarch64_gnullvm" 985 | version = "0.42.0" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" 988 | 989 | [[package]] 990 | name = "windows_aarch64_msvc" 991 | version = "0.42.0" 992 | source = "registry+https://github.com/rust-lang/crates.io-index" 993 | checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" 994 | 995 | [[package]] 996 | name = "windows_i686_gnu" 997 | version = "0.42.0" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" 1000 | 1001 | [[package]] 1002 | name = "windows_i686_msvc" 1003 | version = "0.42.0" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" 1006 | 1007 | [[package]] 1008 | name = "windows_x86_64_gnu" 1009 | version = "0.42.0" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" 1012 | 1013 | [[package]] 1014 | name = "windows_x86_64_gnullvm" 1015 | version = "0.42.0" 1016 | source = "registry+https://github.com/rust-lang/crates.io-index" 1017 | checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" 1018 | 1019 | [[package]] 1020 | name = "windows_x86_64_msvc" 1021 | version = "0.42.0" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" 1024 | 1025 | [[package]] 1026 | name = "wyz" 1027 | version = "0.5.1" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" 1030 | dependencies = [ 1031 | "tap", 1032 | ] 1033 | 1034 | [[package]] 1035 | name = "xattr" 1036 | version = "0.2.3" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" 1039 | dependencies = [ 1040 | "libc", 1041 | ] 1042 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tyler" 3 | version = "0.3.11" 4 | edition = "2021" 5 | authors = ["Balázs Dukai ", "Ravi Peters "] 6 | description = "Create tiles from 3D city objects encoded as CityJSONFeatures." 7 | repository = "https://github.com/3DGI/tyler" 8 | homepage = "https://github.com/3DGI/tyler" 9 | documentation = "https://github.com/3DGI/tyler" 10 | readme = "README.md" 11 | license = "Apache-2.0" 12 | keywords = ["geo", "cityjson"] 13 | categories = ["command-line-utilities", "science::geo"] 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [[bin]] 18 | name = "tyler" 19 | path = "src/main.rs" 20 | 21 | [dependencies] 22 | log = "0.4.17" 23 | env_logger = "0.10.0" 24 | clap = { version = "4.0.32", features = ["cargo", "derive"] } 25 | serde = { version = "1.0.152", features = ["derive"] } 26 | serde_json = "1.0.91" 27 | serde_repr = "0.1.10" 28 | walkdir = "2.3.2" 29 | subprocess = "0.2.9" 30 | rayon = "1.6.1" 31 | # crates needed for the proj adaptation 32 | #proj-sys = { version = "0.23.1" , features = ["network"]} 33 | proj-sys = { version = "0.23.1", features = ["network"], path = "proj/proj-sys" } 34 | num-traits = "0.2.14" 35 | libc = "0.2.119" 36 | thiserror = "1.0.30" 37 | bitvec = "1.0.1" 38 | morton-encoding = "2.0.1" 39 | bincode = "1.3.3" 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Balázs Dukai, Ravi Peters -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tyler 2 | 3 |

4 | 5 |

6 | 7 | *tyler* creates tiles from 3D city objects. 8 | 9 | As input, *tyler* takes [CityJSON Features](https://www.cityjson.org/specs/1.1.3/#text-sequences-and-streaming-with-cityjsonfeature), where each feature is stored in a separate file. 10 | 11 | As output, *tyler* can create: 12 | 13 | - [3D Tiles v1.1](https://docs.ogc.org/cs/22-025r4/22-025r4.html) 14 | 15 | Details of the 3D Tiles output: 16 | 17 | - The tileset content if binary glTF (.glb). 18 | - The glTF assets contain feature metadata (per CityObject), using the [EXT_mesh_features](https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_mesh_features) and [EXT_structural_metadata](https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_structural_metadata) extensions. 19 | - The features are colored to default values, and the colors can by set per CityObject type. 20 | - The glTF files are compressed, using the [KHR_mesh_quantization](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_mesh_quantization) and [EXT_meshopt_compression](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Vendor/EXT_meshopt_compression) extensions. 21 | - Implicit tiling is supported (optional). 22 | 23 | Additional information about the internals of *tyler* you will find in the [design document](https://github.com/3DGI/tyler/blob/master/docs/design_document.md). 24 | 25 | ## Installation 26 | 27 | For the time being, *tyler* depends on the [geoflow-bundle](https://github.com/geoflow3d/geoflow-bundle) for converting CityJSONFeatures to glTF. 28 | Unless you want to install the *geoflow-bundle* yourself, we strongly recommend to use [the provided docker image](https://hub.docker.com/r/3dgi/tyler) for running *tyler*, because it contains the *geoflow-bundle*. 29 | 30 | Pull the docker image with `docker pull 3dgi/tyler:`, e.g. `docker pull 3dgi/tyler:0.3.12`. 31 | 32 | ### Using the pre-compiled binaries on windows 33 | 1. Install [geoflow-bundle](https://github.com/geoflow3d/geoflow-bundle) using the windows installer. Install to the default `C:\Program Files\Geoflow` directory. 34 | 1. Download the latest Tyler binary package for windows from the [Tyler release page](https://github.com/3DGI/tyler/releases) 35 | 1. Unzip the Tyler binary package to a folder, for example `C:\software\tyler` 36 | 1. You can now run Tyler using the `run_tyler_example.bat` file inside this directory by double clicking on it. You can also copy and open this file in a text editor to change the parameters (eg. input and output data directories) used for running. 37 | 38 | For testing purposes you download [this sample data](https://data.3dgi.xyz/3dtiles-test-data/download/3D-basisvoorziening-2021-30dz1_01.zip). Create a `data` folder in same the folder as the `.bat` file mentioned above and unzip the contents there. 39 | 40 | Contents of the `run_tyler_example.bat` file : 41 | 42 | ``` 43 | set RUST_LOG=debug 44 | set TYLER_RESOURCES_DIR=%~dp0\resources 45 | set PROJ_DATA=%~dp0\share\proj 46 | 47 | %~dp0\bin\tyler.exe ^ 48 | --metadata %~dp0\data\metadata.city.json ^ 49 | --features %~dp0\data\30dz2_01 ^ 50 | --output %~dp0\data-out\3dtiles-terrain ^ 51 | --exe-geof "%GF_INSTALL_ROOT%\bin\geof.exe" ^ 52 | --3dtiles-implicit ^ 53 | --object-type LandUse ^ 54 | --object-type PlantCover ^ 55 | --object-type WaterBody ^ 56 | --object-type Road ^ 57 | --object-type GenericCityObject ^ 58 | --object-type Bridge ^ 59 | --object-attribute objectid:int ^ 60 | --object-attribute bronhouder:string ^ 61 | --object-attribute bgt_fysiekvoorkomen:string ^ 62 | --object-attribute bgt_type:string ^ 63 | --3dtiles-metadata-class terrain ^ 64 | --grid-minz=-15 ^ 65 | --grid-maxz=400 >> log.txt 2>&1 66 | ``` 67 | 68 | ### Compiling from source 69 | 70 | *tyler* is written in Rust and you need the [Rust toolchain](https://www.rust-lang.org/learn/get-started) to compile it. 71 | 72 | After downloading the source code from GitHub, navigate into the tyler directory and you can install *tyler* with *cargo*. 73 | 74 | ```shell 75 | cargo install . 76 | ``` 77 | 78 | #### On Windows 79 | 80 | Use [MSYS2](https://www.msys2.org/) with `UCRT64` environment. 81 | 82 | Required libraries (prefix: `mingw-w64-ucrt-x86_64-`): 83 | * clang 84 | * cmake 85 | * libtiff 86 | * make 87 | * rust 88 | * sqlite3 89 | 90 | ## Usage 91 | 92 | *tyler* is a command line application. 93 | 94 | Use `--help` to see the help menu. 95 | 96 | ```shell 97 | tyler --help 98 | ``` 99 | 100 | Execution logs are outputted to the console. 101 | You can control the loging level (`debug`, `info`, `error`) by setting the `RUST_LOG` environment variable. 102 | For instance turn on the debug messages. 103 | 104 | ```shell 105 | RUST_LOG=debug tyler ... 106 | ``` 107 | 108 | Tyler uses the [proj](https://proj.org/) library for reprojecting the input to the required CRS. 109 | The [PROJ_DATA](https://proj.org/usage/environmentvars.html#envvar-PROJ_DATA) environment variable is passed on to the subprocess that generates the glTF files. 110 | 111 | ### Resources directory 112 | 113 | Tyler need two geoflow flowchart files in order to export glTF files. 114 | These files are located in the `resources/geof` directory and they are picked up automatically when the docker image is used. 115 | However, it is also possible to provide their location with the environment variable `TYLER_RESOURCES_DIR`, pointing to the `resources` directory. 116 | For example `export TYLER_RESOURCES_DIR=/some_path/resources`. 117 | 118 | ### Performance 119 | 120 | For large input, like multiple millions of features you need have an SSD. 121 | Running on a HDD is not feasible for large areas. 122 | 123 | There are three resource intensive steps, 1) computing the extent of the input, 2) indexing the input with the grid, 3) converting the tiles. 124 | Each of the three steps are executed concurrently, with the help of the [rayon library](https://crates.io/crates/rayon). 125 | 126 | You can control the level of parallelism by setting the `RAYON_NUM_THREADS` environment variables. 127 | By default *tyler* (rayon) will uses the same number of threads as the number of CPUs available. 128 | Note that on systems with hyperthreading enabled this equals the number of logical cores and not the physical ones. 129 | 130 | ### Calculating the extent and counting features 131 | 132 | The input features (`CityJSONFeature`) are passed in with the `--features` argument, and their type (`CityObject` type) can be restricted with the `--object-type` argument. See above for the details. 133 | 134 | Firstly, *tyler* calculates the complete extent of the input from the bounding box of each feature (of the specified type) that it can find in the `--features` directory tree. 135 | The result of this operation is reported in the logs. 136 | The example below shows that *tyler* found `436` features of type `Building` and `BuildingPart` in `--features`. 137 | The extent of the input data were calculated from these `436` features. 138 | The computed extent is a 3D bounding box in the CRS of the input data, and it is also reported in the logs. 139 | In the example below, the coordinates are in *RD New (EPSG: 7415)*. 140 | 141 | ```commandline 142 | [2023-07-05T08:52:06Z INFO tyler::parser] Found 436 features of type Some([Building, BuildingPart]) 143 | [2023-07-05T08:52:06Z INFO tyler::parser] Ignored 0 features of type [] 144 | [2023-07-05T08:52:06Z DEBUG tyler::parser] extent_qc: BboxQc([-86804720, -26383186, -5333, -86155251, -25703867, 52882]) 145 | [2023-07-05T08:52:06Z DEBUG tyler::parser] Computed extent from features in real-world coordinates: [84995.28, 446316.814, -5.333, 85644.749, 446996.133, 52.882] 146 | ``` 147 | 148 | The extent calculation will be done parallel for each subdirectory of `--features`, if there are any. 149 | The contents of each subdirectory are processed sequentially. 150 | Individual files directly under `--features` are processed sequentially, after the subdirectories. 151 | Therefore, in order to achieve optimal performance, you should organize your features into subdirectories. 152 | 153 | ### Exporting 3D Tiles 154 | 155 | An example command for generating 3D Tiles. 156 | The argument details are explained in the text below. 157 | 158 | ```shell 159 | tyler \ 160 | --metadata metadata.city.json \ 161 | --features features/ \ 162 | --output /3dtiles \ 163 | --3dtiles-implicit \ 164 | --object-type LandUse \ 165 | --object-type PlantCover \ 166 | --object-type WaterBody \ 167 | --object-type Road \ 168 | --object-type GenericCityObject \ 169 | --object-type Bridge \ 170 | --object-attribute objectid:int \ 171 | --object-attribute bronhouder:string \ 172 | --3dtiles-metadata-class terrain \ 173 | --grid-minz=-5 \ 174 | --grid-maxz=300 175 | ``` 176 | 177 | #### Input data 178 | 179 | 1. A main `.city.json` file, containing at least the [CRS](https://www.cityjson.org/specs/1.1.3/#referencesystem-crs) and [transform](https://www.cityjson.org/specs/1.1.3/#transform-object) objects. 180 | 2. A directory (or directory tree) of `.city.jsonl` files, each containing one [CityJSON Feature](https://www.cityjson.org/specs/1.1.3/#text-sequences-and-streaming-with-cityjsonfeature), including all its children City Objects. 181 | 182 | `--metadata` 183 | 184 | A main `.city.json` file, containing at least the `CRS` and `transform` objects, set by the argument. 185 | 186 | `--features` 187 | 188 | A directory (or directory tree) of `.city.jsonl` files, each containing one CityJSON Feature, including all its children City Objects. 189 | 190 | For example: 191 | 192 | `tyler --metadata metadata.city.json --features /some/directory/` 193 | 194 | #### Output 195 | 196 | `--output` 197 | 198 | The output is written to the directory set in `--output`. 199 | For 3D Tiles output, it will contain a `tileset.json` file and `tiles/` directory with the glTF files. 200 | In case of implicit tiling, also a `subtrees/` directory is written with the subtrees. 201 | 202 | During the operation of Tyler, also an `input/` directory is created with text files, but this directory is removed with all its content after Tyler finished processing the tiles (except when debug mode is enabled). 203 | 204 | #### CityObject type 205 | 206 | CityJSON data can contain different types of CityObjects, like Building, PlantCover or Road. 207 | It is possible to only include the selected CityObject types in the tiled output. 208 | The CityObject types are selected with the `--object-type` argument. 209 | This argument can be specified multiple times to select multiple object types. 210 | 211 | For example: 212 | 213 | `tyler … --object-type Building --object-type BuildingPart` 214 | 215 | #### 3D Tiles metadata class 216 | 217 | The 3D Tiles metadata specification uses the concept of classes to categorize features. 218 | With the `--3dtiles-metadata-class` argument it is possible to set the metadata class for the features in the 3D Tiles output. 219 | The metadata class works in conjunction with selecting the CityObject types. Such that one declares a metadata class for a set of CityObject types. 220 | 221 | For example: 222 | 223 | `tyler … --3dtiles-metadata-class building --object-type Building --object-type BuildingPart` 224 | 225 | #### Level of Detail (LoD) 226 | 227 | CityJSON can store city objects with multiple levels of detail. 228 | For each CityObject type, its LoD needs to be specified as well. 229 | This is the LoD defined in the input data. 230 | The LoD value for each CityObject type is set with the `--lod-` arguments. The `` is the CityJSON CityObject type, such as BuildingPart or LandUse. 231 | The arguments are lower-case, thus “BuildingPart” becomes “building-part” and “LandUse” becomes “land-use”. 232 | If the value of `--lod-` is an empty string (this is the default), then Tyler will select the highest available LoD for the city object. 233 | 234 | For example: 235 | 236 | `tyler … --lod-land-use 1 --lod-building-part 1.3` 237 | 238 | #### Attributes 239 | 240 | Attributes on the glTF features are set with the `--object-attribute` argument. 241 | The argument takes the attribute name and attribute value type as its value. 242 | The attribute name and type are separated by a colon “:” and concatenated into a single string, such as “name:type”. 243 | The possible value types are “string”, “int”, “float”, “bool”. 244 | The `--object-attribute` argument can be specified multiple times to include multiple attributes. 245 | 246 | For example: 247 | 248 | `tyler … --object-attribute bouwjaar:int --object-attribute objectid:int --object-attribute bagpandid:string --object-attribute bgt_type:string` 249 | 250 | #### Colors 251 | 252 | Colors on the glTF features are set with the `--color-` arguments. 253 | The `` is the CityJSON CityObject type, such as BuildingPart or LandUse. 254 | The arguments are lower-case, thus “BuildingPart” becomes “building-part” and “LandUse” becomes “land-use”. 255 | The argument value is the hexadecimal rgb color value. For instance “#FF0000” is red. 256 | 257 | For example: 258 | 259 | `tyler … --color-building-part #FF0000` 260 | 261 | #### Bounding volumes 262 | 263 | *tyler* represents the tile's bounding volume as a [Box](https://docs.ogc.org/cs/22-025r4/22-025r4.html#core-box). 264 | 265 | For explicit tilesets, it is possible to add a tightly-fitted bounding volume to the [tile's content](https://docs.ogc.org/cs/22-025r4/22-025r4.html#core-content-bounding-volume). 266 | You can enable this with the `--3dtiles-content-add-bv` option. 267 | 268 | If you do want a content bounding volume, but you want it to follow the tile bounding volume exactly, you can force this with the option `--3dtiles-content-bv-from-tile`. 269 | Usually, this happens for content that is clipped to the tile boundaries, such as terrain. 270 | 271 | ## Debugging 272 | 273 | Run *tyler* in debug mode, by setting the logging level to `debug` in the `RUST_LOG` environment variable. 274 | 275 | ```shell 276 | RUST_LOG=debug tyler ... 277 | ``` 278 | 279 | In debug mode, *tyler* will write the `world`, `quadtree` and `tiles_failed` instances to [bincode](https://crates.io/crates/bincode) to the working directory. 280 | In case of a large area and lots of features (eg. an entire country and multiple millions of features), the `world.bincode` file can become a couple GB in size. 281 | 282 | The bincode files can be loaded by passing the directory with the bincode files to the `--debug-load-data` parameter. When *tyler* load the instance data from the file, it will skip the instance creation and use the loaded data instead. 283 | 284 | The order in which *tyler* creates the instances: 285 | 286 | 1. world 287 | 2. quadtree 288 | 3. tileset 289 | 4. (implicit tileset) 290 | 5. tiles_failed 291 | 6. pruned tileset 292 | 293 | In addition to the instance data, *tyler* can export the grid (part of the `world`), quadtree and tileset data to Tab-separated values (`.tsv`), which you can load into a GIS. 294 | You can enable the `.tsv` export with the `--grid-export` flag. 295 | With the `--grid-export-features` flag, also the feature feature centorids and their grid cell assignment will be exported. 296 | Only use this for small amount of features. 297 | 298 | In debug mode, *tyler* will write the unpruned tileset too, together with the tileset that was pruned after the glTF conversion. 299 | 300 | It is possible to only generate the tileset, without running the glTF conversion. 301 | This can be helpful for debugging the tileset itself. 302 | You can enable this with the `--3dtiles-tileset-only` option. 303 | 304 | ## Roadmap 305 | 306 | - [x] Parallel extent computation 307 | - [x] Parallel grid indexing 308 | - [ ] Integrate the glTF converter to remove the geoflow dependency 309 | - [ ] Integrate cjlib (when it's ready) 310 | - [ ] Read regular CityJSON files, not only CityJSONFeatures 311 | - [ ] Additional export formats: 312 | - [ ] CityJSON 313 | - [ ] Wavefront OBJ 314 | - [ ] GeoPackage 315 | 316 | ## Funding 317 | 318 | Version 0.3 (3D Tiles) was funded by the [Dutch Kadaster](https://www.kadaster.nl/). 319 | -------------------------------------------------------------------------------- /docker/debug.dockerfile: -------------------------------------------------------------------------------- 1 | FROM 3dgi/geoflow-bundle-builder:2024.12.16 AS base 2 | 3 | USER root 4 | 5 | ARG GF_PLUGIN_FOLDER="/usr/local/lib/geoflow-plugins" 6 | 7 | RUN apt-get update && apt-get install -y unzip curl 8 | 9 | # Download Dutch transformation grids 10 | RUN wget https://cdn.proj.org/nl_nsgi_nlgeo2018.tif -O /usr/local/share/proj/nl_nsgi_nlgeo2018.tif && \ 11 | wget https://cdn.proj.org/nl_nsgi_rdtrans2018.tif -O /usr/local/share/proj/nl_nsgi_rdtrans2018.tif 12 | 13 | # Needed for the proj-sys bindings 14 | RUN apt-get install -y clang-15 15 | 16 | # Install rust 17 | RUN curl https://sh.rustup.rs -sSf | sh -s -- -y 18 | 19 | WORKDIR /usr/src/tyler 20 | 21 | COPY Cargo.toml Cargo.lock ./ 22 | COPY resources ./resources 23 | COPY src ./src 24 | COPY proj ./proj 25 | 26 | RUN --mount=type=cache,target=/usr/src/tyler/target-docker CARGO_TARGET_DIR=/usr/src/tyler/target-docker /root/.cargo/bin/cargo build --manifest-path ./Cargo.toml 27 | -------------------------------------------------------------------------------- /docker/strip-docker-image-export: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # NAME 4 | # strip-docker-image-export - exports the bare essentials from a Docker image 5 | # 6 | # SYNOPSIS 7 | # strip-docker-image-export [-d export-dir ] [-p package | -f file] [-v] 8 | # 9 | # 10 | # OPTIONS 11 | # -d export-directory to copy content to, defaults to /export. 12 | # -p package package to include from image, multiple -p allowed. 13 | # -f file file to include from image, multiple -f allowed. 14 | # -v verbose 15 | # 16 | # DESCRIPTION 17 | # this script copies all the files from an installed package and copies them 18 | # to an export directory. Additional files can be added. When an executable 19 | # is copied, all dynamic libraries required by the executed are included too. 20 | # 21 | # EXAMPLE 22 | # The following example strips the nginx installation from the default NGiNX docker image, 23 | # and allows the files in ./export to be added to a scratch image. 24 | # 25 | # docker run -v $PWD/export/:/export \ 26 | # -v $PWD/bin:/mybin nginx \ 27 | # /mybin/strip-image.sh \ 28 | # -p nginx \ 29 | # -f /etc/passwd \ 30 | # -f /etc/group \ 31 | # -f '/lib/*/libnss*' \ 32 | # -f /bin/ls \ 33 | # -f /bin/cat \ 34 | # -f /bin/sh \ 35 | # -f /bin/mkdir \ 36 | # -f /bin/ps \ 37 | # -f /var/run \ 38 | # -f /var/log/nginx \ 39 | # -d /export 40 | # CAVEATS 41 | # requires an image that has a bash, readlink and ldd installed. 42 | # 43 | # AUTHOR 44 | # Mark van Holsteijn 45 | # 46 | # COPYRIGHT 47 | # 48 | # Copyright 2015 Xebia Nederland B.V. 49 | # 50 | # Licensed under the Apache License, Version 2.0 (the "License"); 51 | # you may not use this file except in compliance with the License. 52 | # You may obtain a copy of the License at 53 | # 54 | # http://www.apache.org/licenses/LICENSE-2.0 55 | # 56 | # Unless required by applicable law or agreed to in writing, software 57 | # distributed under the License is distributed on an "AS IS" BASIS, 58 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 59 | # See the License for the specific language governing permissions and 60 | # limitations under the License. 61 | # 62 | export EXPORT_DIR=/export 63 | 64 | function usage() { 65 | echo "usage: $(basename $0) [-v] [-d export-dir ] [-p package | -f file]" >&2 66 | echo " $@" >&2 67 | } 68 | 69 | function parse_commandline() { 70 | 71 | while getopts "vp:f:d:" OPT; do 72 | case "$OPT" in 73 | v) 74 | VERBOSE=v 75 | ;; 76 | p) 77 | PACKAGES="$PACKAGES $OPTARG" 78 | ;; 79 | f) 80 | FILES="$FILES $OPTARG" 81 | ;; 82 | d) 83 | EXPORT_DIR="$OPTARG" 84 | ;; 85 | *) 86 | usage 87 | exit 1 88 | ;; 89 | esac 90 | done 91 | shift $((OPTIND-1)) 92 | 93 | if [ -z "$PACKAGES" -a -z "$FILES" ] ; then 94 | usage "Missing -p or -f options" 95 | exit 1 96 | fi 97 | if [ ! -d $EXPORT_DIR ] ; then 98 | usage "$EXPORT_DIR is not a directory." 99 | exit 1 100 | fi 101 | } 102 | 103 | function print_file() { 104 | if [ "$1" = "/usr/bin/ldd" ]; then 105 | exit 106 | fi 107 | 108 | if [ -e "$1" ] ; then 109 | echo "$1" 110 | else 111 | test -n "$VERBOSE" && echo "INFO: ignoring not existent file '$1'" >&2 112 | fi 113 | 114 | if [ -s "$1" ] ; then 115 | TARGET=$(readlink "$1") 116 | if [ -n "$TARGET" ] ; then 117 | if expr "$TARGET" : '^/' >/dev/null 2>&1 ; then 118 | list_dependencies "$TARGET" 119 | else 120 | list_dependencies $(dirname "$1")/"$TARGET" 121 | fi 122 | fi 123 | fi 124 | } 125 | 126 | function list_dependencies() { 127 | for FILE in $@ ; do 128 | if [ -e "$FILE" ] ; then 129 | print_file "$FILE" 130 | if /usr/bin/ldd "$FILE" >/dev/null 2>&1 ; then 131 | /usr/bin/ldd "$FILE" | \ 132 | awk '/statically/{next;} /=>/ { print $3; next; } { print $1 }' | \ 133 | while read LINE ; do 134 | test -n "$VERBOSE" && echo "INFO: including $LINE" >&2 135 | print_file "$LINE" 136 | done 137 | fi 138 | else 139 | test -n "$VERBOSE" && echo "INFO: ignoring not existent file $FILE" >&2 140 | fi 141 | done 142 | } 143 | 144 | function list_packages() { 145 | if test -e /usr/bin/dpkg; then 146 | DEPS=$(/usr/bin/dpkg -L $1) 147 | elif test -e /usr/bin/rpm; then 148 | DEPS=$(/usr/bin/rpm -ql $1) 149 | elif test -e /sbin/apk; then 150 | DEPS=$(/sbin/apk info -L $1 | grep -Ev '^$|contains:' | sed 's/^/\//g') 151 | else 152 | echo 'WARN: Unknown OS, aborted list_packages()' 153 | exit 154 | fi 155 | while read FILE ; do 156 | if [ ! -d "$FILE" ] ; then 157 | list_dependencies "$FILE" 158 | fi 159 | done <<< "$DEPS" 160 | } 161 | 162 | function list_all_packages() { 163 | for i in "$@" ; do 164 | list_packages "$i" 165 | done 166 | } 167 | 168 | parse_commandline "$@" 169 | 170 | 171 | tar czf - $( 172 | 173 | ( 174 | list_all_packages $PACKAGES 175 | list_dependencies $FILES 176 | ) | sort -u 177 | 178 | ) | ( cd $EXPORT_DIR ; tar -xzh${VERBOSE}f - ) 179 | -------------------------------------------------------------------------------- /docker/tyler.dockerfile: -------------------------------------------------------------------------------- 1 | FROM 3dgi/geoflow-bundle-builder:2025.02.12 AS builder 2 | 3 | USER root 4 | 5 | ARG GF_PLUGIN_FOLDER="/usr/local/lib/geoflow-plugins" 6 | 7 | RUN apt-get update && apt-get install -y unzip curl 8 | 9 | # Download Dutch transformation grids 10 | RUN wget https://cdn.proj.org/nl_nsgi_nlgeo2018.tif -O /usr/local/share/proj/nl_nsgi_nlgeo2018.tif && \ 11 | wget https://cdn.proj.org/nl_nsgi_rdtrans2018.tif -O /usr/local/share/proj/nl_nsgi_rdtrans2018.tif 12 | 13 | # Needed for the proj-sys bindings 14 | RUN apt-get install -y clang-15 15 | 16 | # Install rust 17 | RUN curl https://sh.rustup.rs -sSf | sh -s -- -y 18 | 19 | WORKDIR /usr/src/tyler 20 | COPY Cargo.toml Cargo.lock ./ 21 | COPY resources ./resources 22 | COPY src ./src 23 | COPY proj ./proj 24 | RUN --mount=type=cache,target=/usr/src/tyler/target /root/.cargo/bin/cargo install --path . 25 | 26 | COPY docker/strip-docker-image-export ./ 27 | RUN rm -rf /export 28 | RUN mkdir /export && \ 29 | bash ./strip-docker-image-export \ 30 | -v \ 31 | -d /export \ 32 | -f /usr/local/share/proj/proj.db \ 33 | -f /usr/local/bin/geof \ 34 | -f $GF_PLUGIN_FOLDER/gfp_buildingreconstruction.so \ 35 | -f $GF_PLUGIN_FOLDER/gfp_core_io.so \ 36 | -f $GF_PLUGIN_FOLDER/gfp_gdal.so \ 37 | -f $GF_PLUGIN_FOLDER/gfp_val3dity.so \ 38 | -f $GF_PLUGIN_FOLDER/gfp_las.so \ 39 | -f /root/.cargo/bin/tyler 40 | 41 | FROM ubuntu:lunar-20230301 42 | ARG VERSION 43 | LABEL org.opencontainers.image.authors="Balázs Dukai " 44 | LABEL org.opencontainers.image.vendor="3DGI" 45 | LABEL org.opencontainers.image.title="tyler" 46 | LABEL org.opencontainers.image.description="Create tiles from 3D city objects encoded as CityJSONFeatures." 47 | LABEL org.opencontainers.image.version=$VERSION 48 | LABEL org.opencontainers.image.license="(APACHE-2.0 AND GPL-3 AND AGPL-3)" 49 | 50 | ARG GF_PLUGIN_FOLDER="/usr/local/lib/geoflow-plugins" 51 | 52 | RUN rm -rf /var/lib/apt/lists/* 53 | COPY --from=builder /usr/src/tyler/resources/geof /usr/src/tyler/resources/geof 54 | COPY --from=builder /usr/local/share/proj /usr/local/share/proj 55 | COPY --from=builder $GF_PLUGIN_FOLDER $GF_PLUGIN_FOLDER 56 | COPY --from=builder /export/lib/ /lib/ 57 | COPY --from=builder /export/lib64/ /lib64/ 58 | COPY --from=builder /export/usr/ /usr/ 59 | COPY --from=builder /export/root/.cargo/bin/tyler /usr/local/bin/tyler 60 | 61 | # Update library links 62 | RUN ldconfig 63 | CMD ["tyler"] 64 | -------------------------------------------------------------------------------- /docs/design_document.md: -------------------------------------------------------------------------------- 1 | # Design document 2 | 3 | The goal of Tyler is to create tiles for large areas from 3D city objects that are stored as CityJSON. 4 | In order to tile up large areas efficiently, it only loads the minimum required information from the 3D city objects for creating the tiles and keeps the remaining data on disk. 5 | For fast access to individual city objects, Tyler relies on CityJSONFeature-s. 6 | [CityJSONFeature](https://www.cityjson.org/specs/1.1.3/#text-sequences-and-streaming-with-cityjsonfeature) is part of the CityJSON specification and it allows to store each feature (or city object) in a separate JSON document, or separate file. 7 | In case of Tyler, each CityJSONFeature is stored in a separate file. 8 | 9 | Tyler uses a square grid for indexing and counting the features. 10 | The extent of the grid is determined by visiting each feature, by "walking" the input directory recursively. 11 | In case the features contain outliers, the vertical extent of the grid can be limited (`--grid-min/maxz`). 12 | The 2D cell size of the grid is an argument set by the user (`--grid-cellsize`). 13 | 14 | After the grid, a quadtree is generated bottom-up, by merging each 4 grid cells in Morton-order (a quadrant) if the number of vertices in the quadrant are less than or equal to the configured capacity. 15 | The capacity can be set with the `--qtree-capacity` argument. 16 | Thus, the grid cellsize (`--grid-cellsize`) determines the size of the smallest possible node in the quadtree, the capacity (`--qtree-capacity`) determines the maximum vertex capacity of the leaves of the quadtree. 17 | However, if the grid cellsize is set too large in relation to the quadtree capacity, then the grid cells will not be merged, since they will already contain more vertices than the configured capacity. 18 | On the other hand, if the grid cellsize is too small, then the grid will contain too many cells and the tiling process will take significantly longer and consume more resources. 19 | The default cellsize is 250m and capacity is 42000 vertices. 20 | 21 | The 3D Tiles tileset is created from the quadtree, by transforming the quadtree to the schema that is required by 3D Tiles. 22 | By default, an explicit tiling is created. 23 | It is possible to create implicit tiling with the `--3dtiles-implicit` argument. 24 | 25 | ## Assumptions set by Tyler 26 | 27 | 1. The feature coordinates are expected to be in a projected, Cartesian coordinate reference system with metric units. 28 | 2. If a feature has multiple children, it is expected that the children are adjacent, or at least very close to each other. This is because the whole feature, including all its children are treated as a unit in the tiling process, in order to make sure that all the children of a feature always end up in the same tile. 29 | 30 | ## Input data 31 | 32 | 1. A main `.city.json` file, containing at least the `CRS` and `transform` objects. 33 | 2. A directory (or directory tree) of `.city.jsonl` files, each containing one CityJSON Feature, including all its children City Objects. 34 | 35 | 36 | ## Misc. notes 37 | 38 | Depending on the size of the *feature file path* string (UTF-8) in the `Feature`, the `FeatureSet` might use more or less memory. 39 | 40 | ```shell 41 | echo "b3bd7e17c-deb5-11e7-951f-610a7ca84980.city.jsonl" | wc -m 42 | # 49 characters 43 | 44 | echo "/data/3DBAGv2/export/cityjson/v210908_fd2cee53/b3bd7e17c-deb5-11e7-951f-610a7ca84980.city.jsonl" | wc -m 45 | # 96 characters 46 | ``` 47 | 48 | Then in Python we have in bytes: 49 | 50 | ```python 51 | assert(sys.getsizeof("b3bd7e17c-deb5-11e7-951f-610a7ca84980.city.jsonl") == 97) 52 | 53 | assert(sys.getsizeof("/data/3DBAGv2/export/cityjson/v210908_fd2cee53/b3bd7e17c-deb5-11e7-951f-610a7ca84980.city.jsonl") == 144) 54 | ``` 55 | 56 | While in Rust (I guess C++ is same), in bytes: 57 | 58 | ```rust 59 | assert_eq!(std::mem::size_of_val("b3bd7e17c-deb5-11e7-951f-610a7ca84980.city.jsonl"), 48); 60 | 61 | assert_eq!(std::mem::size_of_val("/data/3DBAGv2/export/cityjson/v210908_fd2cee53/b3bd7e17c-deb5-11e7-951f-610a7ca84980.city.jsonl"), 95); 62 | ``` 63 | 64 | *Optimization A: Parse the .city.jsonl files asynchronously.* 65 | 66 | *Optimization B: Use a MapReduce paradigm (single machine) where we have parallel .city.jsonl --> feature_tuple mappers and their result is written into multiple feature_sets. The number of feature_sets depend on the level or parallelism, and a feature_set is identified by the first Nx2 bits of the morton code of a feature. Where N is the level of parallelism (more or less could work like this I think). Need to take care when updating a feature_set from multiple processes. Finally, merge (reduce) the multiple feature_sets into a single feature_set.* 67 | 68 | #### Struct of arrays to store features 69 | 70 | Instead of having an array of structs for the features, we should consider a struct of arrays. 71 | And therefore benefit from cache-locality when looping over the features in order. 72 | For this to work, the features probably should be in some spatial order. 73 | 74 | Array of structs: 75 | 76 | ```rust 77 | struct Feature { 78 | centroid_qc: [i64; 2], 79 | nr_vertices: u16, 80 | path_jsonl: PathBuf, 81 | bbox_qc: BboxQc, 82 | } 83 | 84 | type FeatureSet = Vec; 85 | ``` 86 | 87 | Struct of arrays: 88 | 89 | ```rust 90 | struct FeatureSet { 91 | centroid_qc: Vec<[i64; 2]>, 92 | nr_vertices: Vec, 93 | path_jsonl: Vec, 94 | bbox_qc: Vec, 95 | } 96 | 97 | type FeatureID = usize; 98 | 99 | impl FeatureSet { 100 | fn get(&self, id: &FeatureID) -> (&[i64;2], &u16, &PathBuf, &BboxQc) { 101 | (&self.centroid_qc[*id], &self.nr_vertices[*id], &self.path_jsonl[*id], &self.bbox_qc[*id]) 102 | } 103 | } 104 | ``` 105 | 106 | #### Estimating memory use for a country like Netherlands 107 | 108 | The Netherlands extent: `Polygon ((13565.3984375 306846.1875, 278026.125 306846.1875, 278026.125 619315.6875, 13565.3984375 619315.6875, 13565.3984375 306846.1875))` 109 | 110 | y-side = 312469.5 111 | x-side = 264460.7 112 | 113 | Square grid: 312469.5 x 312469.5 meters. 114 | Assuming a cellsize of 250m. 115 | Nr. cells = `ceil(312469.5/250) = 12499^2 = 156225001`. 116 | 117 | We store the square grid in 3D vector, where the 3rd dimension is the cell contents. 118 | So we have one vector for the x-side, one vector for each y "row", and one vector for each cell that stores the pointers to the features. 119 | The features are stored in a separate container and grid cell stores pointers to them. 120 | So we have 1 + 521 + 156225001 vectors, plus number of features x `usize`. 121 | An empty Vec is 128bit, and let's assume 10mio features which are all the buildings in the Netherlands. 122 | 123 | Then a square grid with 250m cells, covering the Netherlands and indexing 10mio features is `(1 + 521 + 156225001) × 128 + 10000000 × 64` bits = 2.6Gb. 124 | 125 | #### Merging CityJSONFeatures in Python and writing gltf 126 | 127 | A simple cjio-based script merges a list of `.city.jsonl` files and exports a `.glb` from it. 128 | 129 | ```python 130 | import json 131 | from sys import argv 132 | from pathlib import Path 133 | 134 | from cjio.cityjson import CityJSON 135 | 136 | 137 | def merge(cityjson_path, features_dir): 138 | lcount = 1 139 | #-- read first line 140 | with Path(cityjson_path).resolve().open("r") as fo: 141 | j1 = json.load(fo) 142 | cm = CityJSON(j=j1) 143 | if "CityObjects" not in cm.j: 144 | cm.j["CityObjects"] = {} 145 | if "vertices" not in cm.j: 146 | cm.j["vertices"] = [] 147 | for child in Path(features_dir).iterdir(): 148 | if child.suffix == ".jsonl": 149 | with child.open("r") as fo: 150 | j1 = json.load(fo) 151 | if not( "type" in j1 and j1["type"] == 'CityJSONFeature'): 152 | raise IOError("Line {} is not of type 'CityJSONFeature'.".format(lcount)) 153 | cm.add_cityjsonfeature(j1) 154 | return cm 155 | 156 | if __name__ == "__main__": 157 | cm = merge(argv[1], argv[2]) 158 | glb = cm.export2glb() 159 | glb.seek(0) 160 | with Path(argv[3]).open("wb") as bo: 161 | bo.write(glb.getvalue()) 162 | ``` 163 | 164 | Testing on *3dbag_v21031_7425c21b_5910.json*, which is 2.7Mb on disk, triangulated faces, 436 CityObjects, ~30k vertices. 165 | 166 | `/usr/bin/time -v` reports 56 seconds, 127Mb memory use. 167 | 168 | ``` 169 | User time (seconds): 56.34 170 | System time (seconds): 0.64 171 | Percent of CPU this job got: 101% 172 | Elapsed (wall clock) time (h:mm:ss or m:ss): 0:56.08 173 | Average shared text size (kbytes): 0 174 | Average unshared data size (kbytes): 0 175 | Average stack size (kbytes): 0 176 | Average total size (kbytes): 0 177 | Maximum resident set size (kbytes): 127740 178 | Average resident set size (kbytes): 0 179 | Major (requiring I/O) page faults: 0 180 | Minor (reclaiming a frame) page faults: 38536 181 | Voluntary context switches: 20 182 | Involuntary context switches: 977 183 | Swaps: 0 184 | File system inputs: 0 185 | File system outputs: 9376 186 | Socket messages sent: 0 187 | Socket messages received: 0 188 | Signals delivered: 0 189 | Page size (bytes): 4096 190 | Exit status: 0 191 | ``` 192 | -------------------------------------------------------------------------------- /docs/img/ecef.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3DGI/tyler/f4685eda1f6f5a51a2c5304209f9bef05ad5249d/docs/img/ecef.jpg -------------------------------------------------------------------------------- /resources/data/3dbag_feature_x71.city.jsonl: -------------------------------------------------------------------------------- 1 | {"type":"CityJSONFeature","CityObjects":{"1205507":{"type":"Building","geometry":[{"type":"Solid","lod":"1.2","boundaries":[[[[0,1,2]],[[3,4,5]],[[6,7,8]],[[9,4,10]],[[5,4,11]],[[0,12,1]],[[8,7,10]],[[13,14,12]],[[15,16,17]],[[13,12,0]],[[15,17,18]],[[17,0,2]],[[19,0,17]],[[13,20,14]],[[20,21,22]],[[23,21,20]],[[22,24,25]],[[26,22,25]],[[24,27,25]],[[28,29,27]],[[27,30,25]],[[28,27,24]],[[30,31,25]],[[32,33,30]],[[31,34,25]],[[33,31,30]],[[20,22,26]],[[20,26,14]],[[17,2,18]],[[10,7,2]],[[2,7,18]],[[10,2,9]],[[35,36,37]],[[36,38,37]],[[39,36,40]],[[41,37,42]],[[35,37,41]],[[37,38,43]],[[40,36,35]],[[38,44,43]],[[44,45,43]],[[37,43,9]],[[46,47,9]],[[43,46,9]],[[48,49,47]],[[48,47,46]],[[11,4,9]],[[47,11,9]],[[50,1,51]],[[51,1,12]],[[52,14,53]],[[53,14,26]],[[54,29,55]],[[55,29,28]],[[56,20,57]],[[57,20,13]],[[58,18,59]],[[59,18,7]],[[60,10,61]],[[61,10,4]],[[62,46,63]],[[63,46,43]],[[55,28,64]],[[64,28,24]],[[65,11,66]],[[66,11,47]],[[67,49,68]],[[68,49,48]],[[69,35,70]],[[70,35,41]],[[71,39,72]],[[72,39,40]],[[73,45,74]],[[74,45,44]],[[75,3,76]],[[76,3,5]],[[77,19,78]],[[78,19,17]],[[79,6,80]],[[80,6,8]],[[57,13,81]],[[81,13,0]],[[81,0,77]],[[77,0,19]],[[82,2,50]],[[50,2,1]],[[83,84,82]],[[82,84,2]],[[78,17,85]],[[85,17,16]],[[86,15,58]],[[58,15,18]],[[59,7,79]],[[79,7,6]],[[80,8,60]],[[60,8,10]],[[76,5,65]],[[65,5,11]],[[66,47,67]],[[67,47,49]],[[68,48,62]],[[62,48,46]],[[63,43,73]],[[73,43,45]],[[87,34,88]],[[88,34,31]],[[88,31,89]],[[89,31,33]],[[89,33,90]],[[90,33,32]],[[91,42,92]],[[92,42,37]],[[93,25,87]],[[87,25,34]],[[90,32,94]],[[94,32,30]],[[61,4,75]],[[75,4,3]],[[85,16,86]],[[86,16,15]],[[92,37,95]],[[95,37,9]],[[94,30,96]],[[96,30,27]],[[96,27,54]],[[54,27,29]],[[53,26,93]],[[93,26,25]],[[51,12,52]],[[52,12,14]],[[97,38,98]],[[98,38,36]],[[74,44,97]],[[97,44,38]],[[99,21,100]],[[100,21,23]],[[72,40,69]],[[69,40,35]],[[100,23,56]],[[56,23,20]],[[64,24,101]],[[101,24,22]],[[101,22,99]],[[99,22,21]],[[98,36,71]],[[71,36,39]],[[70,41,91]],[[91,41,42]],[[57,81,51]],[[93,87,88]],[[89,94,88]],[[96,64,93]],[[85,86,78]],[[96,93,94]],[[90,94,89]],[[60,82,59]],[[101,53,93]],[[55,96,54]],[[94,93,88]],[[64,101,93]],[[99,100,56]],[[59,82,58]],[[64,96,55]],[[101,99,56]],[[53,101,56]],[[52,56,57]],[[53,56,52]],[[51,81,50]],[[52,57,51]],[[81,82,50]],[[77,78,81]],[[78,82,81]],[[58,82,78]],[[86,58,78]],[[80,59,79]],[[83,82,60]],[[83,60,61]],[[60,59,80]],[[65,61,76]],[[61,75,76]],[[83,61,65]],[[83,65,66]],[[83,66,62]],[[66,67,68]],[[66,68,62]],[[83,62,63]],[[92,63,97]],[[63,73,74]],[[63,74,97]],[[92,97,98]],[[92,98,69]],[[98,71,72]],[[98,72,69]],[[92,69,70]],[[92,70,91]],[[83,63,92]]]]},{"type":"Solid","lod":"1.3","boundaries":[[[[10,9,4]],[[3,102,5]],[[103,6,104]],[[4,105,106]],[[107,108,109]],[[4,11,105]],[[13,110,12]],[[103,104,111]],[[6,103,8]],[[111,104,112]],[[111,7,10]],[[111,112,7]],[[113,13,12]],[[13,14,114]],[[115,116,117]],[[116,15,16]],[[116,16,117]],[[115,117,118]],[[115,118,17]],[[13,114,110]],[[115,17,18]],[[1,113,12]],[[19,0,17]],[[13,119,14]],[[27,120,121]],[[122,123,124]],[[123,23,21]],[[123,21,124]],[[122,124,125]],[[122,125,22]],[[30,120,27]],[[122,22,20]],[[126,120,30]],[[127,128,28]],[[129,128,127]],[[28,29,127]],[[130,128,129]],[[130,27,24]],[[130,129,27]],[[126,30,25]],[[30,31,25]],[[131,132,32]],[[131,32,33]],[[133,132,131]],[[134,132,133]],[[133,31,30]],[[134,133,30]],[[31,34,25]],[[27,121,24]],[[121,135,24]],[[20,22,135]],[[135,22,24]],[[20,136,13]],[[20,135,136]],[[136,119,13]],[[119,26,14]],[[113,0,13]],[[17,113,137]],[[17,0,113]],[[17,137,18]],[[137,138,18]],[[138,7,18]],[[139,7,138]],[[10,7,139]],[[10,139,2]],[[10,2,9]],[[4,9,140]],[[4,140,141]],[[47,11,142]],[[46,143,144]],[[145,146,35]],[[146,41,35]],[[42,41,147]],[[147,41,146]],[[42,147,37]],[[107,109,39]],[[148,108,107]],[[145,35,149]],[[36,108,148]],[[109,40,39]],[[150,36,38]],[[149,36,150]],[[35,108,36]],[[35,36,149]],[[151,152,153]],[[150,38,144]],[[151,153,44]],[[152,154,153]],[[44,45,151]],[[144,43,46]],[[154,152,43]],[[43,144,38]],[[154,43,38]],[[141,142,11]],[[143,46,47]],[[155,156,157]],[[155,157,48]],[[155,48,49]],[[156,158,157]],[[159,106,105]],[[158,156,47]],[[47,142,143]],[[158,47,46]],[[141,11,4]],[[102,106,159]],[[159,5,102]],[[160,1,161]],[[161,1,12]],[[162,14,163]],[[163,14,26]],[[164,31,165]],[[166,164,165]],[[165,31,133]],[[167,168,169]],[[169,168,30]],[[30,168,134]],[[170,27,171]],[[172,170,171]],[[171,27,129]],[[173,29,174]],[[174,29,28]],[[175,176,177]],[[177,176,24]],[[24,176,130]],[[178,179,180]],[[180,179,20]],[[20,179,122]],[[180,20,181]],[[181,20,13]],[[182,117,183]],[[183,117,16]],[[184,15,185]],[[185,15,116]],[[186,18,187]],[[187,18,7]],[[188,10,189]],[[189,10,4]],[[190,46,191]],[[191,46,43]],[[192,193,194]],[[194,193,195]],[[150,195,149]],[[195,193,149]],[[196,124,197]],[[197,124,21]],[[174,28,198]],[[198,28,128]],[[199,11,200]],[[200,11,47]],[[201,156,202]],[[203,201,202]],[[202,156,155]],[[204,49,205]],[[205,49,48]],[[206,207,208]],[[208,207,209]],[[210,211,162]],[[162,211,14]],[[14,211,114]],[[212,213,214]],[[214,213,215]],[[216,35,217]],[[217,35,41]],[[218,39,219]],[[219,39,40]],[[220,198,176]],[[176,198,130]],[[130,198,128]],[[171,129,221]],[[222,171,221]],[[221,129,127]],[[223,224,225]],[[225,224,226]],[[227,45,228]],[[228,45,44]],[[229,230,231]],[[231,230,158]],[[158,230,157]],[[232,233,168]],[[168,233,134]],[[134,233,132]],[[234,235,236]],[[236,235,237]],[[238,239,240]],[[240,239,241]],[[242,3,243]],[[243,3,5]],[[244,19,245]],[[245,19,17]],[[246,118,182]],[[247,246,182]],[[182,118,117]],[[248,6,249]],[[249,6,8]],[[250,125,196]],[[251,250,196]],[[196,125,124]],[[252,139,253]],[[253,139,138]],[[254,255,256]],[[256,255,253]],[[181,13,257]],[[257,13,0]],[[257,0,244]],[[244,0,19]],[[258,141,259]],[[259,141,140]],[[260,147,261]],[[261,147,146]],[[262,263,160]],[[160,263,1]],[[1,263,113]],[[264,110,211]],[[211,110,114]],[[224,135,265]],[[265,135,121]],[[225,226,266]],[[266,226,267]],[[268,269,270]],[[270,269,271]],[[272,2,252]],[[273,272,252]],[[252,2,139]],[[273,252,274]],[[274,252,275]],[[276,145,193]],[[193,145,149]],[[277,278,279]],[[280,277,279]],[[143,278,144]],[[279,278,143]],[[270,271,281]],[[281,271,282]],[[283,112,284]],[[285,283,284]],[[284,112,104]],[[285,284,286]],[[286,284,287]],[[286,287,288]],[[288,287,111]],[[111,287,103]],[[289,290,291]],[[291,290,259]],[[292,84,272]],[[272,84,2]],[[293,258,294]],[[294,258,295]],[[280,279,296]],[[296,279,297]],[[194,195,234]],[[234,195,235]],[[298,148,299]],[[300,298,299]],[[299,148,107]],[[300,299,301]],[[301,299,302]],[[301,302,303]],[[303,302,108]],[[108,302,109]],[[266,267,304]],[[304,267,265]],[[305,106,306]],[[307,305,306]],[[306,106,102]],[[308,309,310]],[[310,309,105]],[[105,309,159]],[[245,17,246]],[[311,245,246]],[[246,17,118]],[[312,313,186]],[[186,313,18]],[[18,313,115]],[[187,7,283]],[[314,187,283]],[[283,7,112]],[[315,288,188]],[[188,288,10]],[[10,288,111]],[[314,283,315]],[[315,283,288]],[[189,4,305]],[[316,189,305]],[[305,4,106]],[[317,310,199]],[[199,310,11]],[[11,310,105]],[[200,47,201]],[[318,200,201]],[[201,47,156]],[[318,201,319]],[[319,201,231]],[[319,231,190]],[[190,231,46]],[[46,231,158]],[[191,43,320]],[[321,191,320]],[[320,43,152]],[[240,241,206]],[[206,241,207]],[[322,36,298]],[[323,322,298]],[[298,36,148]],[[323,298,324]],[[324,298,303]],[[324,303,216]],[[216,303,35]],[[35,303,108]],[[325,326,327]],[[327,326,328]],[[329,330,331]],[[331,330,332]],[[296,297,325]],[[325,297,326]],[[333,334,335]],[[335,334,336]],[[335,336,337]],[[337,336,338]],[[339,34,164]],[[164,34,31]],[[165,133,340]],[[341,165,340]],[[340,133,131]],[[342,33,343]],[[343,33,32]],[[213,120,344]],[[344,120,126]],[[214,215,345]],[[345,215,346]],[[347,348,254]],[[254,348,255]],[[349,261,350]],[[350,261,276]],[[145,276,146]],[[276,261,146]],[[351,352,333]],[[333,352,334]],[[353,250,178]],[[178,250,179]],[[256,253,330]],[[329,256,330]],[[137,253,138]],[[330,253,137]],[[294,295,289]],[[289,295,290]],[[331,332,354]],[[354,332,355]],[[356,42,357]],[[357,42,37]],[[358,25,339]],[[339,25,34]],[[343,32,233]],[[233,32,132]],[[340,131,342]],[[342,131,33]],[[350,276,351]],[[351,276,352]],[[359,360,349]],[[349,360,261]],[[279,143,361]],[[361,143,142]],[[327,328,362]],[[362,328,361]],[[172,171,175]],[[175,171,176]],[[222,221,220]],[[220,221,198]],[[205,48,230]],[[230,48,157]],[[203,202,229]],[[229,202,230]],[[202,155,204]],[[204,155,49]],[[316,305,317]],[[317,305,310]],[[306,102,242]],[[242,102,3]],[[243,5,309]],[[309,5,159]],[[307,306,308]],[[308,306,309]],[[228,44,363]],[[363,44,153]],[[364,365,366]],[[366,365,363]],[[365,151,227]],[[227,151,45]],[[311,246,312]],[[312,246,313]],[[183,16,184]],[[184,16,15]],[[357,37,260]],[[367,357,260]],[[260,37,147]],[[269,119,368]],[[368,119,136]],[[369,370,371]],[[371,370,368]],[[281,282,369]],[[369,282,370]],[[169,30,170]],[[170,30,27]],[[221,127,173]],[[173,127,29]],[[371,368,224]],[[223,371,224]],[[135,368,136]],[[224,368,135]],[[163,26,269]],[[268,163,269]],[[269,26,119]],[[337,338,192]],[[192,338,193]],[[161,12,264]],[[372,161,264]],[[264,12,110]],[[166,165,167]],[[167,165,168]],[[341,340,232]],[[232,340,233]],[[362,361,293]],[[293,361,258]],[[141,258,142]],[[258,361,142]],[[373,38,322]],[[322,38,36]],[[374,375,373]],[[373,375,38]],[[38,375,154]],[[376,377,374]],[[374,377,375]],[[208,209,376]],[[376,209,377]],[[321,320,238]],[[238,320,239]],[[367,260,378]],[[378,260,379]],[[378,379,359]],[[359,379,360]],[[304,265,212]],[[212,265,213]],[[120,213,121]],[[213,265,121]],[[380,381,179]],[[179,381,122]],[[122,381,123]],[[251,196,380]],[[380,196,381]],[[197,21,382]],[[382,21,23]],[[219,40,302]],[[302,40,109]],[[383,384,347]],[[347,384,348]],[[274,275,383]],[[383,275,384]],[[195,150,278]],[[278,150,144]],[[385,386,277]],[[277,386,278]],[[236,237,385]],[[385,237,386]],[[320,152,365]],[[364,320,365]],[[365,152,151]],[[366,363,375]],[[375,363,154]],[[154,363,153]],[[372,264,387]],[[387,264,388]],[[389,390,210]],[[210,390,211]],[[382,23,381]],[[381,23,123]],[[291,259,391]],[[391,259,9]],[[9,259,140]],[[345,346,392]],[[392,346,344]],[[392,344,358]],[[358,344,25]],[[25,344,126]],[[177,24,393]],[[393,24,22]],[[393,22,250]],[[353,393,250]],[[250,22,125]],[[387,388,389]],[[389,388,390]],[[249,8,287]],[[287,8,103]],[[284,104,248]],[[248,104,6]],[[247,182,394]],[[394,182,185]],[[394,185,313]],[[313,185,115]],[[115,185,116]],[[299,107,218]],[[218,107,39]],[[217,41,356]],[[356,41,42]],[[330,137,263]],[[263,137,113]],[[395,396,262]],[[262,396,263]],[[354,355,395]],[[395,355,396]],[[198,221,174]],[[174,221,173]],[[265,267,226]],[[224,265,226]],[[222,220,171]],[[171,220,176]],[[251,380,179]],[[250,251,179]],[[341,232,165]],[[165,232,168]],[[255,348,253]],[[252,348,384]],[[275,252,384]],[[252,253,348]],[[284,248,287]],[[287,248,249]],[[361,328,279]],[[279,326,297]],[[279,328,326]],[[243,309,306]],[[242,243,306]],[[185,182,183]],[[184,185,183]],[[288,283,285]],[[286,288,285]],[[307,308,305]],[[305,308,310]],[[303,298,300]],[[301,303,300]],[[334,338,336]],[[276,334,352]],[[276,193,334]],[[334,193,338]],[[344,346,215]],[[213,344,215]],[[268,270,389]],[[372,387,161]],[[163,268,162]],[[170,266,214]],[[162,389,210]],[[162,268,389]],[[389,270,181]],[[181,270,281]],[[266,177,225]],[[214,345,169]],[[371,223,369]],[[169,166,167]],[[223,225,369]],[[358,164,345]],[[304,212,266]],[[164,166,169]],[[212,214,266]],[[214,169,170]],[[392,358,345]],[[358,339,164]],[[170,172,175]],[[266,170,177]],[[225,177,393]],[[164,169,345]],[[393,178,180]],[[225,393,369]],[[353,178,393]],[[177,170,175]],[[181,281,180]],[[180,281,369]],[[387,389,181]],[[393,180,369]],[[395,257,354]],[[395,181,257]],[[383,188,274]],[[244,245,257]],[[245,312,186]],[[311,312,245]],[[188,289,274]],[[187,315,188]],[[289,291,292]],[[314,315,187]],[[296,190,191]],[[199,200,294]],[[189,317,199]],[[316,317,189]],[[337,234,322]],[[385,191,236]],[[200,319,190]],[[318,319,200]],[[373,236,206]],[[321,238,191]],[[373,206,376]],[[238,240,191]],[[373,376,374]],[[208,376,206]],[[234,236,373]],[[217,359,216]],[[350,351,349]],[[322,324,216]],[[323,324,322]],[[378,359,217]],[[367,378,357]],[[192,194,234]],[[236,240,206]],[[378,217,356]],[[357,378,356]],[[351,335,216]],[[351,333,335]],[[385,277,280]],[[351,216,359]],[[349,351,359]],[[335,337,322]],[[216,335,322]],[[236,191,240]],[[325,190,296]],[[294,362,293]],[[234,373,322]],[[192,234,337]],[[296,385,280]],[[296,191,385]],[[325,327,200]],[[199,294,289]],[[200,190,325]],[[294,200,327]],[[199,289,189]],[[289,188,189]],[[272,273,274]],[[289,292,274]],[[294,327,362]],[[292,272,274]],[[187,383,347]],[[187,254,186]],[[331,256,329]],[[187,188,383]],[[254,187,347]],[[254,256,331]],[[254,331,186]],[[354,245,331]],[[257,245,354]],[[331,245,186]],[[161,387,395]],[[387,181,395]],[[395,262,160]],[[395,160,161]],[[290,295,258]],[[259,290,258]],[[203,229,201]],[[201,229,231]],[[360,379,260]],[[261,360,260]],[[218,219,302]],[[299,218,302]],[[233,340,343]],[[343,340,342]],[[230,202,205]],[[205,202,204]],[[363,365,228]],[[228,365,227]],[[368,370,282]],[[269,282,271]],[[269,368,282]],[[366,375,377]],[[241,239,209]],[[209,366,377]],[[241,209,207]],[[364,366,239]],[[364,239,320]],[[239,366,209]],[[278,386,237]],[[195,237,235]],[[195,278,237]],[[211,390,388]],[[264,211,388]],[[381,196,197]],[[382,381,197]],[[247,394,313]],[[246,247,313]],[[263,396,355]],[[330,263,332]],[[332,263,355]]]]},{"type":"Solid","lod":"2.2","boundaries":[[[[10,9,4]],[[3,102,5]],[[103,6,104]],[[4,105,106]],[[107,108,109]],[[4,11,105]],[[13,110,12]],[[103,104,111]],[[6,103,8]],[[111,104,112]],[[111,7,10]],[[111,112,7]],[[113,13,12]],[[13,14,114]],[[115,116,117]],[[116,15,16]],[[116,16,117]],[[115,117,118]],[[115,118,17]],[[13,114,110]],[[115,17,18]],[[1,113,12]],[[19,0,17]],[[13,119,14]],[[27,120,121]],[[122,123,124]],[[123,23,21]],[[123,21,124]],[[122,124,125]],[[122,125,22]],[[30,120,27]],[[122,22,20]],[[126,120,30]],[[127,128,28]],[[129,128,127]],[[28,29,127]],[[130,128,129]],[[130,27,24]],[[130,129,27]],[[126,30,25]],[[30,31,25]],[[131,132,32]],[[131,32,33]],[[133,132,131]],[[134,132,133]],[[133,31,30]],[[134,133,30]],[[31,34,25]],[[27,121,24]],[[121,135,24]],[[20,22,135]],[[135,22,24]],[[20,136,13]],[[20,135,136]],[[136,119,13]],[[119,26,14]],[[113,0,13]],[[17,113,137]],[[17,0,113]],[[17,137,18]],[[137,138,18]],[[138,7,18]],[[139,7,138]],[[10,7,139]],[[10,139,2]],[[10,2,9]],[[4,9,140]],[[4,140,141]],[[47,11,142]],[[46,143,144]],[[145,146,35]],[[146,41,35]],[[397,41,147]],[[147,41,146]],[[42,41,397]],[[42,397,37]],[[107,109,39]],[[148,108,107]],[[145,35,149]],[[36,108,148]],[[109,40,39]],[[150,36,38]],[[149,36,150]],[[35,108,36]],[[35,36,149]],[[151,152,153]],[[150,38,144]],[[151,153,44]],[[152,154,153]],[[44,45,151]],[[144,43,46]],[[154,152,43]],[[43,144,38]],[[154,43,38]],[[141,142,11]],[[143,46,47]],[[155,156,157]],[[155,157,48]],[[155,48,49]],[[156,158,157]],[[159,106,105]],[[158,156,47]],[[47,142,143]],[[158,47,46]],[[141,11,4]],[[102,106,159]],[[159,5,102]],[[398,1,399]],[[399,1,12]],[[162,14,163]],[[163,14,26]],[[164,31,400]],[[166,164,400]],[[400,31,133]],[[167,401,169]],[[169,401,30]],[[30,401,134]],[[170,27,402]],[[172,170,402]],[[402,27,129]],[[403,29,404]],[[404,29,28]],[[175,176,177]],[[177,176,24]],[[24,176,130]],[[178,405,180]],[[180,405,20]],[[20,405,122]],[[180,20,181]],[[181,20,13]],[[406,117,407]],[[407,117,16]],[[408,15,116]],[[409,408,116]],[[186,18,187]],[[187,18,7]],[[188,10,189]],[[189,10,4]],[[190,46,191]],[[191,46,43]],[[192,410,411]],[[194,192,411]],[[150,410,149]],[[411,410,150]],[[412,124,413]],[[413,124,21]],[[404,28,128]],[[414,404,128]],[[199,11,200]],[[200,11,47]],[[415,156,416]],[[417,415,416]],[[416,156,155]],[[418,49,419]],[[419,49,48]],[[206,420,421]],[[208,206,421]],[[210,422,162]],[[162,422,14]],[[14,422,114]],[[212,423,424]],[[214,212,424]],[[216,35,217]],[[217,35,41]],[[425,39,40]],[[426,425,40]],[[427,414,176]],[[176,414,130]],[[130,414,128]],[[428,129,127]],[[429,402,428]],[[428,402,129]],[[223,430,431]],[[225,223,431]],[[432,45,44]],[[433,432,44]],[[434,435,436]],[[436,435,158]],[[158,435,157]],[[437,438,401]],[[401,438,134]],[[134,438,132]],[[234,439,440]],[[236,234,440]],[[238,441,240]],[[240,441,442]],[[443,3,444]],[[444,3,5]],[[244,19,245]],[[245,19,17]],[[246,118,406]],[[445,246,406]],[[406,118,117]],[[446,6,447]],[[447,6,8]],[[412,125,124]],[[448,449,412]],[[412,449,125]],[[450,139,138]],[[451,450,138]],[[254,452,256]],[[256,452,451]],[[181,13,257]],[[257,13,0]],[[257,0,244]],[[244,0,19]],[[453,141,140]],[[454,453,140]],[[455,147,146]],[[456,455,146]],[[457,458,398]],[[398,458,1]],[[1,458,113]],[[459,110,422]],[[422,110,114]],[[430,135,460]],[[460,135,121]],[[225,431,266]],[[266,431,461]],[[268,462,463]],[[270,268,463]],[[272,2,450]],[[273,272,450]],[[450,2,139]],[[273,450,464]],[[274,273,464]],[[465,145,149]],[[410,465,149]],[[277,466,467]],[[280,277,467]],[[143,466,144]],[[467,466,143]],[[270,463,468]],[[281,270,468]],[[283,112,469]],[[470,283,469]],[[469,112,104]],[[470,469,471]],[[471,469,472]],[[471,472,473]],[[473,472,111]],[[111,472,103]],[[289,474,291]],[[291,474,454]],[[292,84,272]],[[272,84,2]],[[293,453,475]],[[294,293,475]],[[280,467,476]],[[296,280,476]],[[194,411,439]],[[234,194,439]],[[477,148,107]],[[478,479,477]],[[477,479,148]],[[478,477,480]],[[481,478,480]],[[481,480,482]],[[482,480,108]],[[108,480,109]],[[266,461,304]],[[304,461,460]],[[483,106,484]],[[485,483,484]],[[484,106,102]],[[486,487,488]],[[488,487,105]],[[105,487,159]],[[489,490,491]],[[491,490,492]],[[245,17,246]],[[311,245,246]],[[246,17,118]],[[312,493,186]],[[186,493,18]],[[18,493,115]],[[187,7,283]],[[314,187,283]],[[283,7,112]],[[315,473,188]],[[188,473,10]],[[10,473,111]],[[314,283,473]],[[315,314,473]],[[189,4,483]],[[316,189,483]],[[483,4,106]],[[317,488,199]],[[199,488,11]],[[11,488,105]],[[200,47,415]],[[318,200,415]],[[415,47,156]],[[318,415,436]],[[319,318,436]],[[319,436,190]],[[190,436,46]],[[46,436,158]],[[191,43,494]],[[321,191,494]],[[494,43,152]],[[240,442,420]],[[206,240,420]],[[322,36,479]],[[323,322,479]],[[479,36,148]],[[323,479,482]],[[324,323,482]],[[324,482,216]],[[216,482,35]],[[35,482,108]],[[491,492,495]],[[495,492,496]],[[325,497,327]],[[327,497,498]],[[329,499,500]],[[331,329,500]],[[296,476,497]],[[325,296,497]],[[333,501,502]],[[335,333,502]],[[335,502,337]],[[337,502,503]],[[339,34,164]],[[164,34,31]],[[504,133,131]],[[505,400,504]],[[504,400,133]],[[506,33,507]],[[507,33,32]],[[423,120,508]],[[508,120,126]],[[214,424,509]],[[345,214,509]],[[347,510,452]],[[254,347,452]],[[349,456,465]],[[350,349,465]],[[145,456,146]],[[465,456,145]],[[351,511,501]],[[333,351,501]],[[353,449,178]],[[178,449,405]],[[256,451,499]],[[329,256,499]],[[137,451,138]],[[499,451,137]],[[294,475,474]],[[289,294,474]],[[331,500,512]],[[354,331,512]],[[513,378,514]],[[514,378,515]],[[356,42,357]],[[357,42,37]],[[358,25,339]],[[339,25,34]],[[507,32,132]],[[438,507,132]],[[504,131,506]],[[506,131,33]],[[350,465,511]],[[351,350,511]],[[359,516,349]],[[349,516,456]],[[514,515,517]],[[518,514,517]],[[467,143,142]],[[519,467,142]],[[327,498,362]],[[362,498,519]],[[172,402,176]],[[175,172,176]],[[429,428,427]],[[427,428,414]],[[419,48,435]],[[435,48,157]],[[417,416,435]],[[434,417,435]],[[416,155,49]],[[418,416,49]],[[495,496,520]],[[520,496,521]],[[520,521,522]],[[523,520,522]],[[523,522,490]],[[489,523,490]],[[316,483,488]],[[317,316,488]],[[484,102,3]],[[443,484,3]],[[444,5,487]],[[487,5,159]],[[485,484,487]],[[486,485,487]],[[433,44,524]],[[524,44,153]],[[525,526,524]],[[527,525,524]],[[526,151,45]],[[432,526,45]],[[311,246,493]],[[312,311,493]],[[407,16,408]],[[408,16,15]],[[357,37,517]],[[517,37,397]],[[462,119,528]],[[528,119,136]],[[369,529,371]],[[371,529,528]],[[281,468,369]],[[369,468,529]],[[169,30,170]],[[170,30,27]],[[428,127,403]],[[403,127,29]],[[371,528,430]],[[223,371,430]],[[135,528,136]],[[430,528,135]],[[163,26,462]],[[268,163,462]],[[462,26,119]],[[337,503,192]],[[192,503,410]],[[399,12,459]],[[530,399,459]],[[459,12,110]],[[530,459,531]],[[532,530,531]],[[531,459,533]],[[166,400,401]],[[167,166,401]],[[505,504,437]],[[437,504,438]],[[362,519,293]],[[293,519,453]],[[141,453,142]],[[453,519,142]],[[373,38,322]],[[322,38,36]],[[374,534,373]],[[373,534,38]],[[38,534,154]],[[376,535,534]],[[374,376,534]],[[208,421,535]],[[376,208,535]],[[321,494,441]],[[238,321,441]],[[518,517,536]],[[536,517,455]],[[147,455,397]],[[455,517,397]],[[536,455,378]],[[513,536,378]],[[378,455,537]],[[378,537,516]],[[359,378,516]],[[304,460,423]],[[212,304,423]],[[120,460,121]],[[423,460,120]],[[538,381,405]],[[405,381,122]],[[122,381,123]],[[448,412,538]],[[538,412,381]],[[413,21,539]],[[539,21,23]],[[426,40,480]],[[480,40,109]],[[383,540,510]],[[347,383,510]],[[274,464,540]],[[383,274,540]],[[411,150,144]],[[466,411,144]],[[385,541,277]],[[277,541,466]],[[236,440,385]],[[385,440,541]],[[526,152,151]],[[525,494,526]],[[526,494,152]],[[527,524,534]],[[534,524,154]],[[154,524,153]],[[531,533,387]],[[387,533,542]],[[389,543,210]],[[210,543,422]],[[539,23,123]],[[381,539,123]],[[532,531,544]],[[545,532,544]],[[291,454,391]],[[391,454,9]],[[9,454,140]],[[345,509,392]],[[392,509,508]],[[392,508,358]],[[358,508,25]],[[25,508,126]],[[177,24,393]],[[393,24,22]],[[393,22,449]],[[353,393,449]],[[449,22,125]],[[387,542,389]],[[389,542,543]],[[447,8,103]],[[472,447,103]],[[469,104,446]],[[446,104,6]],[[445,406,546]],[[546,406,409]],[[546,409,493]],[[493,409,115]],[[115,409,116]],[[477,107,39]],[[425,477,39]],[[545,544,547]],[[547,544,548]],[[217,41,356]],[[356,41,42]],[[499,137,113]],[[458,499,113]],[[549,395,457]],[[457,395,458]],[[458,395,550]],[[354,512,395]],[[395,512,550]],[[414,428,404]],[[404,428,403]],[[460,461,431]],[[430,460,431]],[[429,427,402]],[[402,427,176]],[[448,538,405]],[[449,448,405]],[[505,437,400]],[[400,437,401]],[[452,510,451]],[[450,510,540]],[[464,450,540]],[[450,451,510]],[[469,446,472]],[[472,446,447]],[[519,498,467]],[[467,497,476]],[[467,498,497]],[[444,487,484]],[[443,444,484]],[[409,406,407]],[[408,409,407]],[[473,283,470]],[[471,473,470]],[[485,486,483]],[[483,486,488]],[[482,479,478]],[[481,482,478]],[[501,503,502]],[[465,501,511]],[[465,410,501]],[[501,410,503]],[[508,509,424]],[[423,508,424]],[[223,225,369]],[[337,234,322]],[[272,274,292]],[[190,200,319]],[[292,289,291]],[[186,254,331]],[[273,274,272]],[[274,289,292]],[[187,254,186]],[[521,496,281]],[[270,389,268]],[[181,521,180]],[[329,331,256]],[[331,254,256]],[[544,531,387]],[[544,387,492]],[[172,175,170]],[[490,544,492]],[[281,180,521]],[[163,268,162]],[[162,389,210]],[[268,389,162]],[[266,177,225]],[[358,164,345]],[[169,166,167]],[[345,169,214]],[[223,369,371]],[[214,266,212]],[[358,339,164]],[[170,266,214]],[[212,266,304]],[[257,354,490]],[[316,317,189]],[[490,548,544]],[[358,345,392]],[[354,245,331]],[[169,170,214]],[[274,383,188]],[[169,345,164]],[[166,169,164]],[[353,178,393]],[[254,187,347]],[[177,393,225]],[[347,187,383]],[[177,266,170]],[[175,177,170]],[[393,369,225]],[[244,245,257]],[[311,312,245]],[[180,281,369]],[[245,312,186]],[[180,369,393]],[[178,180,393]],[[270,281,496]],[[245,354,257]],[[187,315,188]],[[245,186,331]],[[315,187,314]],[[289,274,188]],[[289,188,189]],[[187,188,383]],[[189,317,199]],[[296,190,191]],[[236,206,373]],[[199,294,289]],[[189,199,289]],[[200,294,199]],[[376,373,206]],[[319,200,318]],[[385,191,236]],[[236,240,206]],[[324,216,322]],[[238,191,321]],[[240,191,238]],[[236,373,234]],[[515,356,357]],[[376,206,208]],[[374,373,376]],[[351,335,216]],[[378,359,217]],[[324,322,323]],[[378,217,515]],[[515,357,517]],[[349,350,351]],[[515,217,356]],[[359,216,217]],[[333,335,351]],[[192,194,234]],[[351,216,359]],[[349,351,359]],[[240,236,191]],[[335,337,322]],[[216,335,322]],[[280,296,385]],[[234,373,322]],[[192,234,337]],[[296,191,385]],[[280,385,277]],[[327,200,325]],[[294,200,327]],[[296,325,190]],[[293,294,362]],[[325,200,190]],[[294,327,362]],[[492,387,389]],[[257,490,522]],[[490,354,548]],[[389,496,492]],[[257,522,181]],[[389,270,496]],[[521,181,522]],[[474,475,453]],[[454,474,453]],[[417,434,415]],[[415,434,436]],[[516,537,455]],[[456,516,455]],[[425,426,480]],[[477,425,480]],[[438,504,507]],[[507,504,506]],[[536,513,514]],[[518,536,514]],[[435,416,419]],[[419,416,418]],[[520,523,495]],[[495,489,491]],[[495,523,489]],[[524,526,433]],[[433,526,432]],[[528,529,468]],[[462,468,463]],[[462,528,468]],[[527,534,535]],[[442,441,421]],[[421,527,535]],[[442,421,420]],[[525,527,441]],[[525,441,494]],[[441,527,421]],[[466,541,440]],[[411,440,439]],[[411,466,440]],[[422,543,542]],[[459,422,533]],[[533,422,542]],[[381,412,413]],[[539,381,413]],[[445,546,493]],[[246,445,493]],[[549,457,398]],[[545,549,399]],[[399,549,398]],[[545,399,530]],[[545,530,532]],[[458,550,512]],[[499,458,500]],[[500,458,512]]]],"semantics":{"surfaces":[{"type":"GroundSurface"},{"type":"RoofSurface"},{"type":"WallSurface"},{"type":"WallSurface"}],"values":[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,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,3,3,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,3,3,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,3,3,2,2,2,3,3,2,2,2,3,3,2,2,3,3,3,3,3,3,2,2,2,3,3,2,2,2,3,3,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,3,3,2,2,2,2,2,2,3,3,2,2,2,3,3,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2,2,2,3,3,3,3,2,2,2,2,3,3,3,3,2,2,2,2,3,3,3,3,3,3,2,2,2,2,2,2,2,2,3,3,3,3,3,3,2,2,3,3,3,3,3,3,2,2,3,3,2,2,3,3,3,3,3,3,3,3,2,2,2,2,3,3,2,2,3,3,2,2,3,3,2,2,2,2,2,2,3,3,3,3,2,2,2,2,2,2,2,2,2,2,2,3,3,2,2,2,3,3,3,3,3,3,3,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,2,2,2,2,3,3,3,3,3,2,2,2,2,2,2,2,3,3,2,2,2,2,3,3,3,3,2,2,3,3,3,3,2,2,2,2,2,2,3,3,3,3,2,2,3,3,2,2,2,3,3,2,2,2,2,2,2,2,2,3,3,2,2,2,2,3,3,2,2,2,2,2,3,3,2,2,2,2,3,3,3,3,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]]}}],"attributes":{"fid":13877670,"identificatie":"NL.IMBAG.Pand.0503100000020682","oorspronkelijk_bouwjaar":2006,"status":"Pand in gebruik","geconstateerd":false,"documentdatum":"2017-11-07","documentnummer":"3155153","voorkomenidentificatie":2,"begingeldigheid":"2017-11-07","eindgeldigheid":null,"tijdstipinactief":null,"tijdstipregistratielv":"2017-11-08T12:01:22.407000+01:00","tijdstipeindregistratielv":null,"tijdstipinactieflv":null,"tijdstipnietbaglv":null,"h_maaiveld":-0.319,"dak_type":"multiple horizontal","pw_datum":"2013-12-01","pw_actueel":"yes","pw_bron":"ahn3","reconstructie_methode":"tudelft3d-geoflow","versie_methode":"v21.03.1","kas_warenhuis":false,"ondergronds_type":"above ground","lod11_replace":false,"reconstruction_skipped":false}}},"vertices":[[572288,596803,5014],[561712,585064,5014],[564865,617189,5014],[578200,622703,5014],[575392,622985,5014],[578398,624711,5014],[577145,611973,5014],[574338,612252,5014],[577342,613982,5014],[564877,617305,5014],[574535,614261,5014],[575589,624994,5014],[561832,585052,5014],[571717,590891,5014],[564546,581767,5014],[576287,603243,5014],[576089,601223,5014],[573282,601499,5014],[573481,603519,5014],[572816,596752,5014],[580983,589982,5014],[583230,592874,5014],[582928,589791,5014],[581286,593072,5014],[591724,588928,5014],[602794,577895,5014],[564534,581648,5014],[593665,588738,5014],[592025,592002,5014],[593968,591824,5014],[601039,588014,5014],[603442,587779,5014],[601339,591070,5014],[603741,590833,5014],[603760,587747,5014],[578768,657381,5014],[578573,655392,5014],[570422,673795,5014],[577714,646638,5014],[581379,655113,5014],[581574,657102,5014],[579724,667114,5014],[580285,672827,5014],[577518,644649,5014],[580520,646357,5014],[580325,644368,5014],[576642,635727,5014],[576447,633737,5014],[579449,635454,5014],[579257,633451,5014],[561712,585064,14082],[561832,585052,14082],[564546,581767,14082],[564534,581648,14082],[593968,591824,14082],[592025,592002,14082],[580983,589982,14082],[571717,590891,14082],[573481,603519,14082],[574338,612252,14082],[574535,614261,14082],[575392,622985,14082],[576642,635727,14082],[577518,644649,14082],[591724,588928,14082],[575589,624994,14082],[576447,633737,14082],[579257,633451,14082],[579449,635454,14082],[578768,657381,14082],[579724,667114,14082],[581379,655113,14082],[581574,657102,14082],[580325,644368,14082],[580520,646357,14082],[578200,622703,14082],[578398,624711,14082],[572816,596752,14082],[573282,601499,14082],[577145,611973,14082],[577342,613982,14082],[572288,596803,14082],[564865,617189,14082],[564878,617304,14082],[564878,617304,5014],[576089,601223,14082],[576287,603243,14082],[603760,587747,14082],[603442,587779,14082],[603741,590833,14082],[601339,591070,14082],[580285,672827,14082],[570422,673795,14082],[602794,577895,14082],[601039,588014,14082],[564877,617305,14082],[593665,588738,14082],[577714,646638,14082],[578573,655392,14082],[583230,592874,14082],[581286,593072,14082],[582928,589791,14082],[577038,622820,5014],[576266,614089,5014],[575865,612100,5014],[575616,624991,5014],[575419,622982,5014],[580027,655247,5014],[578795,657378,5014],[580325,657226,5014],[561879,584995,5014],[574563,614258,5014],[574365,612249,5014],[563311,601352,5014],[564148,582248,5014],[573508,603516,5014],[575166,603353,5014],[574570,601372,5014],[573310,601496,5014],[573047,580813,5014],[594907,578669,5014],[588668,579281,5014],[581047,590633,5014],[581216,592360,5014],[583130,591854,5014],[582995,590473,5014],[599346,578233,5014],[593819,590303,5014],[591847,590182,5014],[593747,589574,5014],[591781,589511,5014],[603631,589710,5014],[601152,589167,5014],[603507,588439,5014],[601109,588727,5014],[584129,579726,5014],[577612,580365,5014],[563720,605519,5014],[564338,611813,5014],[564791,616433,5014],[565411,622742,5014],[565839,627109,5014],[566455,633382,5014],[566900,637918,5014],[567532,644354,5014],[569041,659728,5014],[569629,665712,5014],[570066,670164,5014],[578600,655389,5014],[568578,655006,5014],[568023,649357,5014],[579523,644448,5014],[577822,644619,5014],[579258,646483,5014],[577741,646635,5014],[578133,633565,5014],[576474,633734,5014],[578097,635586,5014],[576670,635724,5014],[576926,624859,5014],[561712,585064,14068],[561832,585052,14068],[564546,581767,14068],[564534,581648,14068],[603442,587779,14068],[603507,588439,10931],[603507,588439,14068],[601109,588727,14068],[601109,588727,10931],[601039,588014,14068],[593665,588738,14068],[593747,589574,10981],[593747,589574,14068],[593968,591824,7801],[592025,592002,7801],[591781,589511,14068],[591781,589511,10981],[591724,588928,14068],[581047,590633,14068],[581047,590633,11012],[580983,589982,14068],[571717,590891,14068],[574570,601372,7742],[576089,601223,7742],[576287,603243,7742],[575166,603353,7742],[573481,603519,14068],[574338,612252,14068],[574535,614261,14068],[575392,622985,14068],[576642,635727,14068],[577518,644649,14068],[568578,655006,14068],[568578,655006,11019],[568023,649357,14068],[568023,649357,11018],[583130,591854,7739],[583230,592874,7739],[591847,590182,7801],[575589,624994,14068],[576447,633737,14068],[576474,633734,10985],[578133,633565,7737],[578133,633565,10985],[579257,633451,7737],[579449,635454,7737],[577718,646407,14068],[577718,646407,10995],[577742,646407,14068],[577742,646407,10995],[564148,582248,14068],[564148,582248,11035],[594907,578669,14068],[594907,578669,11038],[595700,579888,14068],[595700,579888,11038],[578768,657381,14068],[579724,667114,14068],[581379,655113,7745],[581574,657102,7745],[591847,590182,10981],[593819,590303,7801],[593819,590303,10981],[584129,579726,14068],[584129,579726,11044],[584948,581000,14068],[584948,581000,11044],[580325,644368,7736],[580520,646357,7736],[578097,635586,10985],[578097,635586,7737],[576670,635724,10985],[601152,589167,10931],[601152,589167,7766],[568618,649358,14068],[568618,649358,11018],[569045,646174,14068],[569045,646174,11018],[577814,644807,14068],[577814,644807,10995],[577565,644845,14068],[577565,644845,10995],[578200,622703,7739],[578398,624711,7739],[572816,596752,14068],[573282,601499,14068],[573310,601496,10946],[574570,601372,10946],[577145,611973,7730],[577342,613982,7730],[582995,590473,11012],[583130,591854,11012],[564791,616433,11000],[564338,611813,11000],[565506,612244,14068],[565506,612244,11000],[564338,611813,14068],[572288,596803,14068],[565839,627109,11017],[565411,622742,11017],[570066,670164,11021],[569629,665712,11021],[563311,601352,14068],[563311,601352,11014],[561879,584995,11035],[588668,579281,11044],[588169,580655,14068],[588169,580655,11044],[573047,580813,14068],[573047,580813,11026],[573514,581864,14068],[573514,581864,11026],[564865,617189,14068],[564791,616433,14068],[565822,615862,14068],[565822,615862,11000],[569041,659728,11019],[567532,644354,14068],[567532,644354,11018],[566900,637918,11023],[566900,637918,14068],[573748,582041,14068],[573748,582041,11026],[574365,612249,10987],[575865,612100,7730],[575865,612100,10987],[576266,614089,10987],[576266,614089,7730],[574563,614258,10987],[566738,623216,14068],[566738,623216,11017],[565411,622742,14068],[564878,617304,14068],[565839,627109,14068],[566882,627214,14068],[566882,627214,11017],[568149,637226,14068],[568149,637226,11023],[578600,655389,10991],[580027,655247,7745],[580027,655247,10991],[580325,657226,10991],[580325,657226,7745],[578795,657378,10991],[588668,579281,14068],[575419,622982,10968],[577038,622820,7739],[577038,622820,10968],[576926,624859,10968],[576926,624859,7739],[575616,624991,10968],[573310,601496,14068],[573508,603516,14068],[573508,603516,10946],[574365,612249,14068],[574563,614258,14068],[575419,622982,14068],[575616,624991,14068],[576474,633734,14068],[576670,635724,14068],[577822,644619,10995],[577822,644619,14068],[578573,655392,14068],[578600,655389,14068],[578795,657378,14068],[568242,636984,14068],[568242,636984,11023],[567639,633916,14068],[567639,633916,11023],[563720,605519,14068],[563720,605519,11014],[564867,604922,14068],[564867,604922,11014],[569821,658515,14068],[569821,658515,11019],[570212,658240,14068],[570212,658240,11019],[569779,655637,14068],[569779,655637,11019],[603760,587747,14068],[603631,589710,7766],[603631,589710,10931],[603741,590833,7766],[601339,591070,7766],[599346,578233,11038],[599151,579652,14068],[599151,579652,11038],[565501,612378,14068],[565501,612378,11000],[569629,665712,14068],[569041,659728,14068],[569866,659628,14068],[569866,659628,11019],[582995,590473,14068],[564300,600843,14068],[564300,600843,11014],[580285,672827,14068],[570422,673795,14068],[602794,577895,14068],[570898,666272,14068],[570898,666272,11021],[566455,633382,11023],[566455,633382,14068],[579258,646483,7736],[579523,644448,10995],[579523,644448,7736],[579258,646483,10995],[570066,670164,14068],[577612,580365,11026],[577750,581688,14068],[577750,581688,11026],[577612,580365,14068],[561879,584995,14068],[577714,646638,14068],[577741,646635,14068],[577741,646635,10995],[577735,646576,14068],[577735,646576,10995],[571238,670284,14068],[571238,670284,11021],[581216,592360,11012],[581216,592360,7739],[581286,593072,7739],[565835,615649,14068],[565835,615649,11000],[568305,644171,14068],[568305,644171,11018],[563654,585027,14068],[563654,585027,11035],[564943,583754,14068],[564943,583754,11035],[564877,617305,14068],[599346,578233,14068],[582928,589791,14068],[575166,603353,10946],[563660,600597,14068],[563660,600597,11014],[570193,671459,5014],[561712,585064,15051],[561832,585052,15009],[603507,588439,10920],[601109,588727,10933],[593747,589574,10973],[593968,591824,7757],[592025,592002,7667],[581047,590633,10947],[574570,601372,7756],[576089,601223,7750],[576287,603243,7712],[575166,603353,7717],[568578,655006,10997],[568023,649357,10997],[583130,591854,7756],[583230,592874,7724],[591847,590182,7808],[576474,633734,10988],[578133,633565,7733],[578133,633565,10971],[579257,633451,7740],[579449,635454,7739],[577718,646407,10993],[577742,646407,10993],[564148,582248,10974],[594907,578669,10992],[595700,579888,11059],[581379,655113,7700],[581574,657102,7789],[591847,590182,10986],[593819,590303,7875],[593819,590303,10978],[584129,579726,11042],[584948,581000,11076],[580325,644368,7720],[580520,646357,7750],[578097,635586,10977],[578097,635586,7731],[576670,635724,10992],[601152,589167,10936],[601152,589167,7721],[568618,649358,11015],[569045,646174,11037],[577814,644807,10982],[577565,644845,10981],[578200,622703,7742],[578398,624711,7742],[574570,601372,10936],[577145,611973,7732],[577342,613982,7705],[583130,591854,11040],[582995,590473,10979],[564791,616433,10980],[564338,611813,10985],[565506,612244,11011],[565839,627109,10987],[565411,622742,11007],[570066,670164,10996],[569629,665712,10996],[563311,601352,15033],[563311,601352,10997],[561879,584995,11064],[588668,579281,10980],[588169,580655,11030],[573047,580813,10987],[573514,581864,11043],[565822,615862,11006],[569041,659728,10997],[567532,644354,10997],[566900,637918,10997],[573748,582041,11053],[575865,612100,7745],[575865,612100,10976],[576266,614089,10982],[576266,614089,7715],[574563,614258,10994],[566738,623216,11038],[566882,627214,11014],[568149,637226,11036],[580027,655247,7663],[580027,655247,10997],[578600,655389,10968],[580325,657226,7755],[580325,657226,11002],[578795,657378,10971],[575419,622982,10970],[577038,622820,7735],[577038,622820,10954],[576926,624859,10960],[576926,624859,7733],[575616,624991,10974],[569026,593251,14996],[569026,593251,14068],[568705,590096,14950],[568705,590096,14068],[573508,603516,10953],[577822,644619,10981],[570883,589846,14851],[570883,589846,14068],[568242,636984,11040],[567639,633916,11031],[563720,605519,10992],[564867,604922,11021],[569821,658515,11023],[570212,658240,11036],[569779,655637,11030],[603631,589710,7795],[603631,589710,10929],[603741,590833,7790],[601339,591070,7713],[599346,578233,10985],[599151,579652,11059],[565501,612378,11010],[569866,659628,11021],[564300,600843,11022],[571238,670284,14925],[571273,670697,14911],[571273,670697,14068],[570898,666272,11032],[570193,671459,14068],[570193,671459,14972],[566455,633382,10998],[571119,589883,14841],[571119,589883,14068],[571257,593091,14068],[571257,593091,14896],[579258,646483,7737],[579523,644448,10992],[579523,644448,7712],[579258,646483,11004],[577612,580365,10967],[577750,581688,11038],[561879,584995,14990],[562133,585000,14068],[562133,585000,14902],[562133,585000,11062],[577741,646635,10994],[577735,646576,10994],[570066,670164,15015],[571238,670284,11030],[581216,592360,11023],[581286,593072,7716],[565835,615649,11007],[568305,644171,11021],[563654,585027,11048],[564943,583754,11004],[562111,585028,14068],[562111,585028,14911],[575166,603353,10941],[563657,600596,14888],[563657,600596,14068],[563660,600597,14887],[563660,600597,11008]],"id":"1205507"} 2 | -------------------------------------------------------------------------------- /resources/data/3dbag_x00.city.json: -------------------------------------------------------------------------------- 1 | {"type":"CityJSON","version":"1.1","CityObjects":{},"vertices":[],"transform":{"scale":[0.001,0.001,0.001],"translate":[84995.2799868164,446316.81397216796,-5.333460330963135]},"metadata":{"identifier":"9cf00322-b20b-438c-ae98-14d871217a26","referenceDate":"2021-03-14","geographicalExtent":[84995.2799868164,446316.81397216796,-5.333460330963135,85644.74900003051,446996.1325909424,52.88161849975586],"referenceSystem":"https://www.opengis.net/def/crs/EPSG/0/7415"},"+metadata-extended":{"datasetCharacterSet":"UTF-8","datasetTopicCategory":"geoscientificInformation","distributionFormatVersion":"1.0","spatialRepresentationType":["vector"],"metadataStandard":"ISO 19115 - Geographic Information - Metadata","metadataStandardVersion":"ISO 19115:2014(E)","metadataCharacterSet":"UTF-8","metadataDateStamp":"2021-03-14","textures":"absent","materials":"absent","cityfeatureMetadata":{"Building":{"uniqueFeatureCount":436,"aggregateFeatureCount":1308,"presentLoDs":{"1.2":436,"1.3":436,"2.2":436}}},"presentLoDs":{"1.2":436,"1.3":436,"2.2":436},"thematicModels":["Building"],"fileIdentifier":"3dbag_v21031_7425c21b_5910.json"},"extensions":{"MetadataExtended":{"url":"https://raw.githubusercontent.com/cityjson/metadata-extended/0.5/metadata-extended.ext.json","version":"0.5"}}} 2 | -------------------------------------------------------------------------------- /resources/data/README.md: -------------------------------------------------------------------------------- 1 | db3dnl extent: 2 | 192892.914, 465155.528, 195998.461, 466860.523 3 | 4 | 3dbag (v210908) 5 | tile 5910 extent: 84995.280, 446316.814, -5.333, 85644.749, 446996.133, 52.882 6 | tile 5910 polygon: Polygon ((85020 446370, 85020 446990, 85640 446990, 85640 446370, 85020 446370)) 7 | ```shell 8 | /usr/bin/time -v target/release/tyler --metadata /data/3DBAG/3dbag_v21031_7425c21b_5910/x00.city.json --features /data/3DBAG/3dbag_v21031_7425c21b_5910 --output /data/3DBAG/out_tmp/glb --format 3dtiles --python-bin /home/balazs/Development/cjio/venv/bin/python --export-grid 9 | ``` 10 | 11 | tile 1-1 in 4978: `Polygon (( 12 | 3923296.09080639528110623 299848.07630294701084495, 13 | 3923278.64884402975440025 300047.31848194636404514, 14 | 3923121.71722133178263903 300032.51704561983933672, 15 | 3923139.15921139623969793 299833.27486105053685606, 16 | 3923296.09080639528110623 299848.07630294701084495))` 17 | 18 | Point(3923223.496401593 299941.4082726164 5003058.505677583), 19 | Point(3923160.3138962006 299941.4082726164 5003058.505677583), 20 | Point(3923223.496401593 299847.3523817994 5003058.505677583), 21 | Point(3923223.496401593 299941.4082726164 5003151.744253726) 22 | 23 | 3923223.496401593 299941.4082726164 5003058.505677583, 24 | Point(3923160.3138962006 299941.4082726164 5003058.505677583), 25 | 3923223.496401593 299847.3523817994 5003058.505677583, 26 | 3923223.496401593 299941.4082726164 5003151.744253726 27 | 28 | What's this? Polygon((171800.0 472700.0, 173198.235 472700.0, 173198.235 473956.134, 171800.0 473956.134, 171800.0 472700.0)) 29 | 30 | ```shell 31 | mkdir "resources/data/features_3dbag_5909" 32 | python resources/python/split_to_features.py resources/data/3dbag_v21031_7425c21b_5909_subset.city.json resources/data/features_3dbag_5909 1 33 | ``` -------------------------------------------------------------------------------- /resources/geof/createGLB.json: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "GF_PROCESS_CRS": [ 4 | "", 5 | "str", 6 | "EPSG:7415" 7 | ], 8 | "GF_PROCESS_OFFSET_X": [ 9 | "offset in X coordinate", 10 | "float", 11 | 210478.234375 12 | ], 13 | "GF_PROCESS_OFFSET_Y": [ 14 | "offset in Y coordinate", 15 | "float", 16 | 468316.65625 17 | ], 18 | "GF_PROCESS_OFFSET_Z": [ 19 | "offset in Z coordinate", 20 | "float", 21 | -15.0 22 | ], 23 | "attribute_spec": [ 24 | "Attribute names and types to output. Format: :,... eg: name1:string,name2:int,name3:float,name", 25 | "str", 26 | "objectid:int,bronhouder:string,bgt_fysiekvoorkomen:string,bgt_type:string" 27 | ], 28 | "colorBridge": [ 29 | "colorBridge hex color (eg #AABBCC)", 30 | "str", 31 | "#4F4A6A" 32 | ], 33 | "colorBridgeConstructionElement": [ 34 | "colorBridgeConstructionElement hex color (eg #AABBCC)", 35 | "str", 36 | "#4F4A6A" 37 | ], 38 | "colorBridgeInstallation": [ 39 | "colorBridgeInstallation hex color (eg #AABBCC)", 40 | "str", 41 | "#4F4A6A" 42 | ], 43 | "colorBridgePart": [ 44 | "colorBridgePart hex color (eg #AABBCC)", 45 | "str", 46 | "#4F4A6A" 47 | ], 48 | "colorBuilding": [ 49 | "colorBuilding hex color (eg #AABBCC)", 50 | "str", 51 | "#EC7658" 52 | ], 53 | "colorBuildingInstallation": [ 54 | "colorBuildingInstallation hex color (eg #AABBCC)", 55 | "str", 56 | "#EC7658" 57 | ], 58 | "colorBuildingPart": [ 59 | "colorBuildingPart hex color (eg #AABBCC)", 60 | "str", 61 | "#EC7658" 62 | ], 63 | "colorCityFurniture": [ 64 | "colorCityFurniture hex color (eg #AABBCC)", 65 | "str", 66 | "#4F4A6A" 67 | ], 68 | "colorGenericCityObject": [ 69 | "colorGenericCityObject hex color (eg #AABBCC)", 70 | "str", 71 | "#4F4A6A" 72 | ], 73 | "colorLandUse": [ 74 | "colorLandUse hex color (eg #AABBCC)", 75 | "str", 76 | "#C3DBB5" 77 | ], 78 | "colorPlantCover": [ 79 | "colorPlantCover hex color (eg #AABBCC)", 80 | "str", 81 | "#A6CD59" 82 | ], 83 | "colorRailway": [ 84 | "colorRailway hex color (eg #AABBCC)", 85 | "str", 86 | "#474447" 87 | ], 88 | "colorRoad": [ 89 | "colorRoad hex color (eg #AABBCC)", 90 | "str", 91 | "#474447" 92 | ], 93 | "colorSolitaryVegetationObject": [ 94 | "colorSolitaryVegetationObject hex color (eg #AABBCC)", 95 | "str", 96 | "#A6CD59" 97 | ], 98 | "colorTINRelief": [ 99 | "colorTINRelief hex color (eg #AABBCC)", 100 | "str", 101 | "#A6CD59" 102 | ], 103 | "colorTransportSquare": [ 104 | "colorTransportSquare hex color (eg #AABBCC)", 105 | "str", 106 | "#474447" 107 | ], 108 | "colorTunnel": [ 109 | "colorTunnel hex color (eg #AABBCC)", 110 | "str", 111 | "#4F4A6A" 112 | ], 113 | "colorTunnelInstallation": [ 114 | "colorTunnelInstallation hex color (eg #AABBCC)", 115 | "str", 116 | "#4F4A6A" 117 | ], 118 | "colorTunnelPart": [ 119 | "colorTunnelPart hex color (eg #AABBCC)", 120 | "str", 121 | "#4F4A6A" 122 | ], 123 | "colorWaterBody": [ 124 | "colorWaterBody hex color (eg #AABBCC)", 125 | "str", 126 | "#293A4A" 127 | ], 128 | "cotypes": [ 129 | "comma separated list of cityobject types. Leave empty for no filter.", 130 | "str", 131 | "LandUse,Road,WaterBody,PlantCover,Bridge,OtherConstruction" 132 | ], 133 | "lodBridge": [ 134 | "lod filter for Bridge features", 135 | "str", 136 | "" 137 | ], 138 | "lodBridgeConstructionElement": [ 139 | "lod filter for BridgeConstructionElement features", 140 | "str", 141 | "" 142 | ], 143 | "lodBridgeInstallation": [ 144 | "lod filter for BridgeInstallation features", 145 | "str", 146 | "" 147 | ], 148 | "lodBridgePart": [ 149 | "lod filter for BridgePart features", 150 | "str", 151 | "" 152 | ], 153 | "lodBuilding": [ 154 | "lod filter for Building features", 155 | "str", 156 | "" 157 | ], 158 | "lodBuildingInstallation": [ 159 | "lod filter for BuildingInstallation features", 160 | "str", 161 | "" 162 | ], 163 | "lodBuildingPart": [ 164 | "lod filter for BuildingPart features", 165 | "str", 166 | "" 167 | ], 168 | "lodCityFurniture": [ 169 | "lod filter for CityFurniture features", 170 | "str", 171 | "" 172 | ], 173 | "lodGenericCityObject": [ 174 | "lod filter for GenericCityObject features", 175 | "str", 176 | "" 177 | ], 178 | "lodLandUse": [ 179 | "lod filter for LandUse features", 180 | "str", 181 | "" 182 | ], 183 | "lodPlantCover": [ 184 | "lod filter for PlantCover features", 185 | "str", 186 | "" 187 | ], 188 | "lodRailway": [ 189 | "lod filter for Railway features", 190 | "str", 191 | "" 192 | ], 193 | "lodRoad": [ 194 | "lod filter for Road features", 195 | "str", 196 | "" 197 | ], 198 | "lodSolitaryVegetationObject": [ 199 | "lod filter for SolitaryVegetationObject features", 200 | "str", 201 | "" 202 | ], 203 | "lodTINRelief": [ 204 | "lod filter for TINRelief features", 205 | "str", 206 | "" 207 | ], 208 | "lodTransportSquare": [ 209 | "lod filter for TransportSquare features", 210 | "str", 211 | "" 212 | ], 213 | "lodTunnel": [ 214 | "lod filter for Tunnel features", 215 | "str", 216 | "" 217 | ], 218 | "lodTunnelInstallation": [ 219 | "lod filter for TunnelInstallation features", 220 | "str", 221 | "" 222 | ], 223 | "lodTunnelPart": [ 224 | "lod filter for TunnelPart features", 225 | "str", 226 | "" 227 | ], 228 | "lodWaterBody": [ 229 | "lod filter for WaterBody features", 230 | "str", 231 | "" 232 | ], 233 | "max_x": [ 234 | "", 235 | "float", 236 | 210773.234375 237 | ], 238 | "max_y": [ 239 | "", 240 | "float", 241 | 468611.65625 242 | ], 243 | "max_z": [ 244 | "", 245 | "float", 246 | 400.0 247 | ], 248 | "metadata_class": [ 249 | "The name of the metadata class to create (for EXT_structural_metadata).", 250 | "str", 251 | "terrain" 252 | ], 253 | "min_x": [ 254 | "", 255 | "float", 256 | 210478.234375 257 | ], 258 | "min_y": [ 259 | "", 260 | "float", 261 | 468316.65625 262 | ], 263 | "min_z": [ 264 | "", 265 | "float", 266 | -15.0 267 | ], 268 | "output_file": [ 269 | "output (glb) file", 270 | "str", 271 | "/mnt/Data-2/3dbv_terrain/t/10/762/525.glb" 272 | ], 273 | "output_format": [ 274 | "glb or city.json", 275 | "str", 276 | "3dtiles" 277 | ], 278 | "path_features_input_file": [ 279 | "text file with on each line an input CityJSON feature path", 280 | "str", 281 | "/mnt/Data-2/3dbv_terrain/inputs/10/762/525.input" 282 | ], 283 | "path_metadata": [ 284 | "CityJSON features metadata file", 285 | "str", 286 | "/mnt/Data-2/CityJSON_export/metadata.city.json" 287 | ], 288 | "simplify_error": [ 289 | "", 290 | "float", 291 | 1.0 292 | ], 293 | "simplify_ratio": [ 294 | "0-1 lower for more simplification", 295 | "float", 296 | 0.05000000074505806 297 | ], 298 | "skip_clip": [ 299 | "", 300 | "bool", 301 | false 302 | ], 303 | "smooth_normals": [ 304 | "", 305 | "bool", 306 | false 307 | ] 308 | }, 309 | "nodes": { 310 | "Box": { 311 | "connections": { 312 | "ping": [ 313 | [ 314 | "processFeatures", 315 | "processFeatures.wait" 316 | ] 317 | ] 318 | }, 319 | "marked_outputs": { 320 | "box": false, 321 | "ping": false 322 | }, 323 | "parameters": { 324 | "inCRS": "epsg:7415", 325 | "max_x": "{{max_x}}", 326 | "max_y": "{{max_y}}", 327 | "max_z": "{{max_z}}", 328 | "min_x": "{{min_x}}", 329 | "min_y": "{{min_y}}", 330 | "min_z": "{{min_z}}" 331 | }, 332 | "position": [ 333 | 226.0, 334 | 172.0 335 | ], 336 | "type": [ 337 | "Core", 338 | "Box" 339 | ] 340 | }, 341 | "CJFeature_paths": { 342 | "connections": { 343 | "value": [ 344 | [ 345 | "processFeatures", 346 | "processFeatures.globals" 347 | ] 348 | ] 349 | }, 350 | "marked_outputs": { 351 | "value": false 352 | }, 353 | "parameters": { 354 | "filepath": "{{path_features_input_file}}", 355 | "limit": 0, 356 | "split": true 357 | }, 358 | "position": [ 359 | 208.0, 360 | 106.0 361 | ], 362 | "type": [ 363 | "Core", 364 | "TextReader" 365 | ] 366 | }, 367 | "GLTFWriter": { 368 | "marked_inputs": { 369 | "attributes": false, 370 | "feature_type": false, 371 | "normals": false, 372 | "triangles": false 373 | }, 374 | "parameters": { 375 | "CRS": "EPSG:4978", 376 | "binary": true, 377 | "colorBridge": "{{colorBridge}}", 378 | "colorBridgeConstructionElement": "{{colorBridgeConstructionElement}}", 379 | "colorBridgeInstallation": "{{colorBridgeInstallation}}", 380 | "colorBridgePart": "{{colorBridgePart}}", 381 | "colorBuilding": "{{colorBuilding}}", 382 | "colorBuildingInstallation": "{{colorBuildingInstallation}}", 383 | "colorBuildingPart": "{{colorBuildingPart}}", 384 | "colorCityFurniture": "{{colorCityFurniture}}", 385 | "colorGenericCityObject": "{{colorGenericCityObject}}", 386 | "colorLandUse": "{{colorLandUse}}", 387 | "colorOtherConstruction": "#4F4A6A", 388 | "colorPlantCover": "{{colorPlantCover}}", 389 | "colorRailway": "{{colorRailway}}", 390 | "colorRoad": "{{colorRoad}}", 391 | "colorSolitaryVegetationObject": "{{colorSolitaryVegetationObject}}", 392 | "colorTINRelief": "{{colorTINRelief}}", 393 | "colorTransportSquare": "{{colorTransportSquare}}", 394 | "colorTunnel": "{{colorTunnel}}", 395 | "colorTunnelInstallation": "{{colorTunnelInstallation}}", 396 | "colorTunnelPart": "{{colorTunnelPart}}", 397 | "colorWaterBody": "{{colorWaterBody}}", 398 | "embed_buffers": true, 399 | "embed_images": true, 400 | "filepath": "{{output_file}}", 401 | "meshopt_compress": true, 402 | "metadata_class": "{{metadata_class}}", 403 | "pretty_print": false, 404 | "quantize_vertex": true, 405 | "relative_to_center": true 406 | }, 407 | "position": [ 408 | 1010.0, 409 | 97.0 410 | ], 411 | "type": [ 412 | "CoreIO", 413 | "GLTFWriter" 414 | ] 415 | }, 416 | "GLTFWriter-gltf": { 417 | "marked_inputs": { 418 | "attributes": false, 419 | "feature_type": false, 420 | "normals": false, 421 | "triangles": false 422 | }, 423 | "parameters": { 424 | "CRS": "EPSG:4978", 425 | "binary": false, 426 | "colorBridge": "#4F4A6A", 427 | "colorBridgeConstructionElement": "#4F4A6A", 428 | "colorBridgeInstallation": "#4F4A6A", 429 | "colorBridgePart": "#4F4A6A", 430 | "colorBuilding": "#EC7658", 431 | "colorBuildingInstallation": "#EC7658", 432 | "colorBuildingPart": "#EC7658", 433 | "colorCityFurniture": "#4F4A6A", 434 | "colorGenericCityObject": "#4F4A6A", 435 | "colorLandUse": "#C3DBB5", 436 | "colorOtherConstruction": "#4F4A6A", 437 | "colorPlantCover": "#A6CD59", 438 | "colorRailway": "#474447", 439 | "colorRoad": "#474447", 440 | "colorSolitaryVegetationObject": "#A6CD59", 441 | "colorTINRelief": "#A6CD59", 442 | "colorTransportSquare": "#474447", 443 | "colorTunnel": "#4F4A6A", 444 | "colorTunnelInstallation": "#4F4A6A", 445 | "colorTunnelPart": "#4F4A6A", 446 | "colorWaterBody": "#293A4A", 447 | "embed_buffers": false, 448 | "embed_images": false, 449 | "filepath": "{{output_file}}.gltf", 450 | "meshopt_compress": true, 451 | "metadata_class": "{{metadata_class}}", 452 | "pretty_print": true, 453 | "quantize_vertex": true, 454 | "relative_to_center": true 455 | }, 456 | "position": [ 457 | 916.0, 458 | 238.0 459 | ], 460 | "type": [ 461 | "CoreIO", 462 | "GLTFWriter" 463 | ] 464 | }, 465 | "processFeatures": { 466 | "connections": { 467 | "CityJSONL2Mesh.attributes": [ 468 | [ 469 | "GLTFWriter", 470 | "attributes" 471 | ] 472 | ], 473 | "CityJSONL2Mesh.feature_type": [ 474 | [ 475 | "GLTFWriter", 476 | "feature_type" 477 | ] 478 | ], 479 | "MeshClipper.normals": [ 480 | [ 481 | "GLTFWriter", 482 | "normals" 483 | ] 484 | ], 485 | "MeshClipper.triangles": [ 486 | [ 487 | "GLTFWriter", 488 | "triangles" 489 | ] 490 | ] 491 | }, 492 | "marked_inputs": { 493 | "processFeatures.globals": false, 494 | "processFeatures.wait": false 495 | }, 496 | "marked_outputs": { 497 | "CityJSONL2Mesh.attributes": false, 498 | "CityJSONL2Mesh.feature_type": false, 499 | "MeshClipper.normals": false, 500 | "MeshClipper.triangles": false, 501 | "processFeatures.timings": false 502 | }, 503 | "parameters": { 504 | "filepath": "process_feature.json", 505 | "push_any_for_empty_sfterminal": false, 506 | "require_input_globals": true, 507 | "require_input_wait": true 508 | }, 509 | "position": [ 510 | 416.0, 511 | 146.0 512 | ], 513 | "type": [ 514 | "Core", 515 | "NestedFlowchart" 516 | ] 517 | } 518 | } 519 | } 520 | -------------------------------------------------------------------------------- /resources/geof/fix_attributes.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import argparse 4 | 5 | def copy_attributes_to_building_parts(cityjson_data): 6 | """Copy attributes from parent Building to its BuildingParts.""" 7 | # Get the main objects 8 | city_objects = cityjson_data.get('CityObjects', {}) 9 | 10 | # Keep track of processed buildings 11 | for obj_id, obj_data in city_objects.items(): 12 | if obj_data.get('type') == 'Building': 13 | # Get parent building attributes 14 | parent_attributes = obj_data.get('attributes', {}) 15 | # Get children (BuildingParts) 16 | children_ids = obj_data.get('children', []) 17 | 18 | # Copy attributes to each child 19 | for child_id in children_ids: 20 | if child_id in city_objects: 21 | child_obj = city_objects[child_id] 22 | if child_obj.get('type') == 'BuildingPart': 23 | # Create attributes dict if it doesn't exist 24 | if 'attributes' not in child_obj: 25 | child_obj['attributes'] = {} 26 | # Update child attributes with parent attributes 27 | child_obj['attributes'].update(parent_attributes) 28 | 29 | return cityjson_data 30 | 31 | def process_cityjson_files(input_folder, output_folder, verbose): 32 | """Recursively find and process CityJSON files.""" 33 | # Create output folder if it doesn't exist 34 | os.makedirs(output_folder, exist_ok=True) 35 | 36 | # Count processed files 37 | processed_count = 0 38 | 39 | # Walk through directory tree 40 | for root, dirs, files in os.walk(input_folder): 41 | for filename in files: 42 | if filename.lower().endswith('.city.jsonl'): 43 | input_path = os.path.join(root, filename) 44 | 45 | # Create corresponding output path 46 | relative_path = os.path.relpath(root, input_folder) 47 | output_dir = os.path.join(output_folder, relative_path) 48 | os.makedirs(output_dir, exist_ok=True) 49 | output_path = os.path.join(output_dir, filename) 50 | 51 | try: 52 | # Read the input file 53 | with open(input_path, 'r', encoding='utf-8') as f: 54 | cityjson_data = json.load(f) 55 | 56 | # Process the data 57 | modified_data = copy_attributes_to_building_parts(cityjson_data) 58 | 59 | # Write the modified data 60 | with open(output_path, 'w', encoding='utf-8') as f: 61 | json.dump(modified_data, f) 62 | 63 | if verbose: 64 | processed_count += 1 65 | print(f"Processed: {input_path} -> {output_path}") 66 | 67 | except Exception as e: 68 | print(f"Error processing {input_path}: {str(e)}") 69 | 70 | if verbose: 71 | print(f"\nProcessing complete. Total files processed: {processed_count}") 72 | 73 | def main(): 74 | # Set up argument parser 75 | parser = argparse.ArgumentParser( 76 | description="Copy attributes from Buildings to descendant BuildingParts in CityJSON files" 77 | ) 78 | parser.add_argument( 79 | "input_folder", 80 | help="Path to the input folder containing CityJSON files" 81 | ) 82 | parser.add_argument( 83 | "output_folder", 84 | help="Path to the output folder for processed files" 85 | ) 86 | parser.add_argument( 87 | "--verbose", 88 | "-v", 89 | action="store_true", 90 | help="Enable verbose output" 91 | ) 92 | 93 | # Parse arguments 94 | args = parser.parse_args() 95 | 96 | # Validate input folder 97 | if not os.path.isdir(args.input_folder): 98 | print(f"Error: Input folder '{args.input_folder}' does not exist!") 99 | return 100 | 101 | if args.verbose: 102 | print(f"Starting processing...") 103 | print(f"Input folder: {args.input_folder}") 104 | print(f"Output folder: {args.output_folder}") 105 | 106 | # Process the files 107 | process_cityjson_files(args.input_folder, args.output_folder, args.verbose) 108 | 109 | if __name__ == "__main__": 110 | main() -------------------------------------------------------------------------------- /resources/geof/process_feature.json: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "GF_PROCESS_OFFSET_OVERRIDE": [ 4 | "", 5 | "bool", 6 | true 7 | ], 8 | "cotypes": [ 9 | "", 10 | "str", 11 | "" 12 | ], 13 | "max_x": [ 14 | "", 15 | "float", 16 | 194964.0 17 | ], 18 | "max_y": [ 19 | "", 20 | "float", 21 | 466309.0 22 | ], 23 | "max_z": [ 24 | "", 25 | "float", 26 | 30.0 27 | ], 28 | "min_x": [ 29 | "", 30 | "float", 31 | 194447.0 32 | ], 33 | "min_y": [ 34 | "", 35 | "float", 36 | 465847.0 37 | ], 38 | "min_z": [ 39 | "", 40 | "float", 41 | 0.0 42 | ], 43 | "path_metadata": [ 44 | "CityJSON features metadata file", 45 | "str", 46 | "/mnt/Data/LocalData/Kadaster/db3dnl_features/metadata.city.json" 47 | ], 48 | "simplify_error": [ 49 | "", 50 | "float", 51 | 1.0 52 | ], 53 | "simplify_ratio": [ 54 | "", 55 | "float", 56 | 0.10000000149011612 57 | ], 58 | "skip_clip": [ 59 | "", 60 | "bool", 61 | false 62 | ], 63 | "smooth_normals": [ 64 | "", 65 | "bool", 66 | false 67 | ] 68 | }, 69 | "nodes": { 70 | "BoundingBox": { 71 | "connections": { 72 | "box": [ 73 | [ 74 | "MeshClipper", 75 | "bbox" 76 | ] 77 | ] 78 | }, 79 | "marked_outputs": { 80 | "box": false, 81 | "ping": false 82 | }, 83 | "parameters": { 84 | "inCRS": "EPSG:7415", 85 | "max_x": "{{max_x}}", 86 | "max_y": "{{max_y}}", 87 | "max_z": "{{max_z}}", 88 | "min_x": "{{min_x}}", 89 | "min_y": "{{min_y}}", 90 | "min_z": "{{min_z}}" 91 | }, 92 | "position": [ 93 | 630.0, 94 | 340.0 95 | ], 96 | "type": [ 97 | "Core", 98 | "Box" 99 | ] 100 | }, 101 | "CJFeatureReader": { 102 | "connections": { 103 | "value": [ 104 | [ 105 | "CityJSONL2Mesh", 106 | "jsonl_features_str" 107 | ] 108 | ] 109 | }, 110 | "marked_outputs": { 111 | "value": false 112 | }, 113 | "parameters": { 114 | "filepath": "{{value}}", 115 | "limit": 0, 116 | "split": false 117 | }, 118 | "position": [ 119 | 267.0, 120 | 148.0 121 | ], 122 | "type": [ 123 | "Core", 124 | "TextReader" 125 | ] 126 | }, 127 | "CJMetadata": { 128 | "connections": { 129 | "value": [ 130 | [ 131 | "CityJSONL2Mesh", 132 | "jsonl_metadata_str" 133 | ] 134 | ] 135 | }, 136 | "marked_outputs": { 137 | "value": false 138 | }, 139 | "parameters": { 140 | "filepath": "{{path_metadata}}", 141 | "limit": 0, 142 | "split": false 143 | }, 144 | "position": [ 145 | 292.0, 146 | 215.0 147 | ], 148 | "type": [ 149 | "Core", 150 | "TextReader" 151 | ] 152 | }, 153 | "CityJSONL2Mesh": { 154 | "connections": { 155 | "meshes": [ 156 | [ 157 | "Mesh2SurfaceMesh", 158 | "mesh" 159 | ] 160 | ] 161 | }, 162 | "marked_inputs": { 163 | "jsonl_features_str": false, 164 | "jsonl_metadata_str": false 165 | }, 166 | "marked_outputs": { 167 | "attributes": true, 168 | "feature_type": true, 169 | "lod0_2d": false, 170 | "meshes": false, 171 | "meshes_attributes": false, 172 | "roofparts": false, 173 | "roofparts_lr": false, 174 | "roofparts_lr_attributes": false 175 | }, 176 | "parameters": { 177 | "3bag_buildings_mode": false, 178 | "atribute_spec": "{{attribute_spec}}", 179 | "bag3d_attr_per_part": true, 180 | "cotypes": "{{cotypes}}", 181 | "lod_filter": { 182 | "Bridge": "{{lodBridge}}", 183 | "BridgeConstructionElement": "{{lodBridgeConstructionElement}}", 184 | "BridgeInstallation": "{{lodBridgeInstallation}}", 185 | "BridgePart": "{{lodBridgePart}}", 186 | "Building": "{{lodBuilding}}", 187 | "BuildingInstallation": "{{lodBuildingInstallation}}", 188 | "BuildingPart": "{{lodBuildingPart}}", 189 | "CityFurniture": "{{lodCityFurniture}}", 190 | "GenericCityObject": "{{lodGenericCityObject}}", 191 | "LandUse": "{{lodLandUse}}", 192 | "PlantCover": "{{lodPlantCover}}", 193 | "Railway": "{{lodRailway}}", 194 | "Road": "{{lodRoad}}", 195 | "SolitaryVegetationObject": "{{lodSolitaryVegetationObject}}", 196 | "TINRelief": "{{lodTINRelief}}", 197 | "TransportSquare": "{{lodTransportSquare}}", 198 | "Tunnel": "{{lodTunnel}}", 199 | "TunnelInstallation": "{{lodTunnelInstallation}}", 200 | "TunnelPart": "{{lodTunnelPart}}", 201 | "WaterBody": "{{lodWaterBody}}" 202 | }, 203 | "optimal_lod": false, 204 | "optimal_lod_value": "2.2" 205 | }, 206 | "position": [ 207 | 426.0, 208 | 164.0 209 | ], 210 | "type": [ 211 | "CoreIO", 212 | "CityJSONL2Mesh" 213 | ] 214 | }, 215 | "Mesh2SurfaceMesh": { 216 | "connections": { 217 | "cgal_surface_mesh": [ 218 | [ 219 | "MeshSimplify2D", 220 | "cgal_surface_mesh" 221 | ] 222 | ] 223 | }, 224 | "marked_inputs": { 225 | "mesh": false 226 | }, 227 | "marked_outputs": { 228 | "cgal_surface_mesh": false 229 | }, 230 | "position": [ 231 | 828.0, 232 | 146.0 233 | ], 234 | "type": [ 235 | "building-reconstruction", 236 | "Mesh2CGALSurfaceMesh" 237 | ] 238 | }, 239 | "MeshClipper": { 240 | "marked_inputs": { 241 | "bbox": false, 242 | "mesh": false 243 | }, 244 | "marked_outputs": { 245 | "cgal_surface_mesh": false, 246 | "normals": true, 247 | "triangles": true 248 | }, 249 | "parameters": { 250 | "cgal_clip": false, 251 | "skip_clip": "{{skip_clip}}", 252 | "smooth_normals": "{{smooth_normals}}" 253 | }, 254 | "position": [ 255 | 892.0, 256 | 279.0 257 | ], 258 | "type": [ 259 | "building-reconstruction", 260 | "MeshClipper" 261 | ] 262 | }, 263 | "MeshSimplify2D": { 264 | "connections": { 265 | "cgal_surface_mesh": [ 266 | [ 267 | "MeshClipper", 268 | "mesh" 269 | ] 270 | ] 271 | }, 272 | "marked_inputs": { 273 | "cgal_surface_mesh": false 274 | }, 275 | "marked_outputs": { 276 | "cgal_surface_mesh": false 277 | }, 278 | "parameters": { 279 | "error": "{{simplify_error}}", 280 | "minpts": 0.05000000074505806 281 | }, 282 | "position": [ 283 | 867.0, 284 | 207.0 285 | ], 286 | "type": [ 287 | "building-reconstruction", 288 | "MeshSimplify2D" 289 | ] 290 | } 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /resources/geof/remove_empty_leafs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2023 Balázs Dukai, Ravi Peters 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | import json 17 | import os, sys 18 | from pathlib import Path 19 | 20 | TILESET_DIR = Path("/dev/shm/3dtiles-buildings") 21 | 22 | # filename = TILESET_DIR / "tileset_og.json" 23 | # output = TILESET_DIR / "tileset.json" 24 | 25 | filename = sys.argv[1] 26 | output = sys.argv[2] 27 | 28 | TILESET_DIR = Path(filename).parent 29 | print(TILESET_DIR) 30 | 31 | def remove_empty_leafs(node): 32 | if node.get("children"): 33 | # print("node has {} children".format(len(node["children"]))) 34 | # print(node["children"]) 35 | new_children = [] 36 | for child in node["children"]: 37 | if child.get("content"): 38 | # print("checking " + str(TILESET_DIR / child["content"]["uri"])) 39 | if(os.path.exists(TILESET_DIR / child["content"]["uri"])): 40 | new_children.append(child) 41 | # print("exists") 42 | else: 43 | new_children.append(child) 44 | if len(new_children) == 0: 45 | del node["children"] 46 | else: 47 | node["children"] = new_children 48 | # print(node["children"]) 49 | for child in node["children"]: 50 | remove_empty_leafs(child) 51 | 52 | with open(filename, "r") as in_file: 53 | tileset = json.load(in_file) 54 | 55 | remove_empty_leafs(tileset["root"]) 56 | 57 | with open(output, "w") as file: 58 | json.dump(tileset, file) 59 | -------------------------------------------------------------------------------- /resources/python/adjust_geometric_error.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2023 Balázs Dukai, Ravi Peters 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | import json 17 | import argparse 18 | from pathlib import Path 19 | 20 | parser=argparse.ArgumentParser(description="Multiply geometric error values by factor. Make sure to back up tileset json files before running.") 21 | parser.add_argument("input_tileset", help="Main tileset input file (.json)") 22 | parser.add_argument("factor", type=float, help="Multiply geometric error values by this factor") 23 | parser.add_argument("--overwrite", action='store_true', help="Overwrite files without warning") 24 | 25 | def adjust_node(node, factor, external_tilesets): 26 | node["geometricError"] = node["geometricError"] * factor 27 | 28 | if node.get("content"): 29 | if node["content"].get("uri"): 30 | if node["content"]["uri"].endswith(".json"): 31 | external_tilesets.append(node["content"]["uri"]) 32 | 33 | if node.get("children"): 34 | for child in node["children"]: 35 | adjust_node(child, factor, external_tilesets) 36 | 37 | def adjust_tileset(tileset_basedir, tileset_filename, factor, overwrite): 38 | tileset_filepath = tileset_basedir / tileset_filename 39 | external_tilesets = [] 40 | with open(tileset_filepath, "r") as in_file: 41 | tileset = json.load(in_file) 42 | 43 | # find and modify geometric error values 44 | tileset["geometricError"] = tileset["geometricError"] * factor 45 | adjust_node(tileset["root"], factor, external_tilesets) 46 | 47 | # save json 48 | if (tileset_filepath).exists() and not overwrite: 49 | overwrite = input(f'Tileset file \'{tileset_filepath}\' already exists. Overwrite and loose original contents? This also affects any external tilesets Y = yes, N = no\n') 50 | if not overwrite.lower() == 'y': exit() 51 | 52 | with open(tileset_filepath, "w") as file: 53 | json.dump(tileset, file) 54 | 55 | # adjust external tilesets if any 56 | for ext_tileset_relative_path in external_tilesets: 57 | adjust_tileset(tileset_basedir, Path(ext_tileset_relative_path), factor, overwrite) 58 | 59 | if __name__ == "__main__": 60 | args = parser.parse_args() 61 | 62 | tileset_path = Path(args.input_tileset) 63 | tileset_filename = tileset_path.name 64 | tileset_basedir = tileset_path.parent 65 | adjust_tileset(tileset_basedir, tileset_filename, args.factor, args.overwrite) 66 | -------------------------------------------------------------------------------- /resources/python/convert_cityjsonfeatures.py: -------------------------------------------------------------------------------- 1 | """Copyright 2023 Balázs Dukai, Ravi Peters""" 2 | import json 3 | import argparse 4 | from sys import argv 5 | from pathlib import Path 6 | from copy import deepcopy 7 | 8 | from cjio.cityjson import CityJSON 9 | 10 | 11 | def merge(cityjson_path, path_features_input_file: Path): 12 | lcount = 1 13 | # -- read first line 14 | with cityjson_path.open("r") as fo: 15 | j1 = json.load(fo) 16 | cm = CityJSON(j=j1) 17 | if "CityObjects" not in cm.j: 18 | cm.j["CityObjects"] = {} 19 | if "vertices" not in cm.j: 20 | cm.j["vertices"] = [] 21 | with path_features_input_file.open("r") as input_file: 22 | for p in input_file: 23 | path = Path(p.strip("\n")).resolve() 24 | if path.suffix == ".jsonl": 25 | with path.open("r") as fo: 26 | j1 = json.load(fo) 27 | if not ("type" in j1 and j1["type"] == 'CityJSONFeature'): 28 | raise IOError( 29 | "Line {} is not of type 'CityJSONFeature'.".format(lcount)) 30 | cm.add_cityjsonfeature(j1) 31 | else: 32 | print(f"Not a .jsonl file {path}, suffix: {path.suffix}") 33 | path_features_input_file.unlink() 34 | return cm 35 | 36 | 37 | parser=argparse.ArgumentParser() 38 | parser.add_argument("--output_format", help="Format to convert to") 39 | parser.add_argument("--output_file", help="Where to save the output") 40 | parser.add_argument("--path_metadata", help="The main .city.json file with the transformation properties") 41 | parser.add_argument("--path_features_input_file", help="File with the list of feature paths") 42 | parser.add_argument("--min_x", help="Bounding box minimum x coordinate") 43 | parser.add_argument("--min_y", help="Bounding box minimum y coordinate") 44 | parser.add_argument("--min_z", help="Bounding box minimum z coordinate") 45 | parser.add_argument("--max_x", help="Bounding box maximum x coordinate") 46 | parser.add_argument("--max_y", help="Bounding box maximum y coordinate") 47 | parser.add_argument("--max_z", help="Bounding box maximum z coordinate") 48 | parser.add_argument("--cotypes", help="Comma separated list of CityObject types to include in the tile") 49 | parser.add_argument("--metadata_class", help="The name of the metadata class to create (for EXT_structural_metadata).") 50 | parser.add_argument("--attribute_spec", help="The CityObject attribute to include in the output.") 51 | parser.add_argument("--geometric_error") 52 | 53 | if __name__ == "__main__": 54 | args = parser.parse_args() 55 | # format to convert to 56 | supported_formats = ["cityjson", "3dtiles"] 57 | output_format = args.output_format 58 | if output_format not in supported_formats: 59 | raise ValueError(f"Output format {output_format} is not supported. Supported formats: {supported_formats}") 60 | # where to save the output 61 | output_file = Path(args.output_file) 62 | output_file.parent.mkdir(parents=True, exist_ok=True) 63 | # the main .city.json file with the transformation properties 64 | cityjson_path = Path(args.path_metadata).resolve() 65 | # a file with the list of feature paths 66 | path_features_input_file = Path(args.path_features_input_file).resolve() 67 | # tile bbox (used in the terrain splitter) 68 | bbox = [args.min_x, args.min_y, args.min_z, args.max_x, args.max_y, args.max_z] 69 | # CityObject types to use 70 | cotypes = args.cotypes.split(",") 71 | 72 | cm = merge(cityjson_path, path_features_input_file) 73 | nr_co_after_merge = len(cm.j["CityObjects"]) 74 | types_after_merge = cm.get_info() 75 | if output_format == "cityjson": 76 | with output_file.open("w") as fo: 77 | fo.write(json.dumps(cm.j, separators=(',', ':'))) 78 | elif output_format == "3dtiles": 79 | cm.reproject(4978) 80 | cm = cm.get_subset_cotype(cotypes) 81 | nr_co_after_subset = len(cm.j["CityObjects"]) 82 | if nr_co_after_subset == 0: 83 | if nr_co_after_merge > 0: 84 | print(f"CityModel {output_file} does not contain any {cotypes} objects, but it did contain {nr_co_after_merge} objects of type {types_after_merge}.") 85 | else: 86 | print( 87 | f"CityModel {output_file} does not contain any {cotypes} objects and it did not contain any objects at all after merge either.") 88 | glb = cm.export2glb(do_triangulate=False) 89 | glb.seek(0) 90 | with output_file.open("wb") as bo: 91 | bo.write(glb.getvalue()) 92 | 93 | # # TODO: we are cheating here, because we know that the data has 3 LoD-s and we 94 | # # also hardcoded the same into Tileset.from() in tyler. Same for the tile/file 95 | # # names. 96 | # # lod_file_names = [("1.2", ""), ("1.3", "-0"), ("2.2", "-0-0")] # grid with multi-lod 97 | # # lod_file_names = [("2.2", "-0-0"),] # for grid with single lod 98 | # lod_file_names = [("2.2", ""),] # for quadtree 99 | # for lod, suffix in lod_file_names: 100 | # cm_copy = deepcopy(cm) 101 | # cm_copy.filter_lod(lod) 102 | # glb = cm_copy.export2glb(do_triangulate=False) 103 | # glb.seek(0) 104 | # output_file_tile = (output_file.parent / (output_file.stem + suffix)).with_suffix(".glb") 105 | # with output_file_tile.open("wb") as bo: 106 | # bo.write(glb.getvalue()) 107 | else: 108 | raise ValueError("unsupported format and we should have reached this branch anyway") 109 | 110 | # /home/bdukai/software/cjio/venv_38/lib/python3.8/site-packages/pyproj/transformer.py:197: UserWarning: Best transformation is not available due to missing Grid(short_name=nl_nsgi_nlgeo2018.tif, full_name=, package_name=, url=https://cdn.proj.org/nl_nsgi_nlgeo2018.tif, direct_download=True, open_license=True, available=False) -------------------------------------------------------------------------------- /resources/python/print_quadtree.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2023 Balázs Dukai, Ravi Peters 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | from pathlib import Path 17 | import json 18 | 19 | tileset_path = Path("/data/work/bdukai/3dbag_v2103_features/3dtiles/tileset_explicit.json") 20 | with tileset_path.open("r") as fo: 21 | tileset = json.load(fo) 22 | 23 | 24 | def printTree(root, markerStr="+- ", levelMarkers=[]): 25 | """https://simonhessner.de/python-3-recursively-print-structured-tree-including-hierarchy-markers-using-depth-first-search/""" 26 | emptyStr = " " * len(markerStr) 27 | connectionStr = "|" + emptyStr[:-1] 28 | level = len(levelMarkers) 29 | mapper = lambda draw: connectionStr if draw else emptyStr 30 | markers = "".join(map(mapper, levelMarkers[:-1])) 31 | markers += markerStr if level > 0 else "" 32 | if "content" in root: 33 | print(f"{markers}{level}: {root['geometricError']} {root['content']['uri']}") 34 | else: 35 | print(f"{markers}{level}: {root['geometricError']} {root['content']['uri']}") 36 | if "children" in root: 37 | for i, child in enumerate(root["children"]): 38 | isLast = i == len(root["children"]) - 1 39 | printTree(child, markerStr, [*levelMarkers, not isLast]) 40 | 41 | 42 | if __name__ == "__main__": 43 | printTree(tileset["root"]) 44 | -------------------------------------------------------------------------------- /resources/python/split_to_features.py: -------------------------------------------------------------------------------- 1 | """Split a directory of cityjson files to cityjsonfeature files, one file per feature. 2 | One main cityjson file (metadata.city.json) is written for all the features and the 3 | 'transform' property is computed from the extent of all files. 4 | 5 | Copyright 2023 Balázs Dukai, Ravi Peters 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | """ 19 | from pathlib import Path 20 | from sys import argv 21 | from os import cpu_count 22 | from concurrent.futures import ProcessPoolExecutor, as_completed 23 | import json 24 | 25 | from cjio import cityjson 26 | 27 | # zwaartepunt in Nederland 28 | TRANSLATE = [171800.0, 472700.0, 0.0] 29 | IMPORTANT_DIGITS = 3 30 | 31 | input_dir = Path(argv[1]).resolve() 32 | output_dir = Path(argv[2]).resolve() 33 | max_workers = int(argv[3]) if int(argv[3]) <= cpu_count() else cpu_count() 34 | 35 | if input_dir.is_dir(): 36 | cityjson_path_list = [cityjson_path for cityjson_path in input_dir.iterdir()] 37 | else: 38 | cityjson_path_list = [input_dir, ] 39 | if len(cityjson_path_list) <= max_workers: 40 | max_workers = len(cityjson_path_list) 41 | print(f"Using maximum {max_workers} workers") 42 | 43 | 44 | # --- Compute translation properties from the extent 45 | 46 | # Compute the extent of all the files 47 | def bbox_from_file(path: Path): 48 | with path.open(mode='r', encoding='utf-8-sig') as f: 49 | cm = cityjson.reader(file=f, ignore_duplicate_keys=False) 50 | return cm.get_bbox() 51 | 52 | 53 | futures = [] 54 | 55 | # Init the extent 56 | with cityjson_path_list[0].open(mode='r', encoding='utf-8-sig') as f: 57 | cm = cityjson.reader(file=f, ignore_duplicate_keys=False) 58 | extent = cm.calculate_bbox() 59 | 60 | with ProcessPoolExecutor(max_workers=max_workers) as executor: 61 | for cj_path in cityjson_path_list: 62 | futures.append(executor.submit(bbox_from_file, cj_path)) 63 | for i, future in enumerate(as_completed(futures)): 64 | minx, miny, minz, maxx, maxy, maxz = future.result() 65 | if minx < extent[0]: 66 | extent[0] = minx 67 | if miny < extent[1]: 68 | extent[1] = miny 69 | if minz < extent[2]: 70 | extent[2] = minz 71 | if maxx > extent[3]: 72 | extent[3] = maxx 73 | if maxy > extent[4]: 74 | extent[4] = maxx 75 | if maxz > extent[5]: 76 | extent[5] = maxz 77 | del cm, i, future, futures, executor 78 | 79 | # The features are centered around the center of the extent 80 | dx = extent[3] - extent[0] 81 | dy = extent[4] - extent[1] 82 | dz = extent[5] - extent[2] 83 | center = [extent[0] + dx * 0.5, extent[1] + dy * 0.5, extent[2] + dz * 0.5] 84 | translate = TRANSLATE 85 | print(f"Computed translation property: {translate}") 86 | 87 | 88 | # --- Write the metadata file 89 | with cityjson_path_list[0].open(mode='r', encoding='utf-8-sig') as f: 90 | cm = cityjson.reader(file=f, ignore_duplicate_keys=False) 91 | cm.upgrade_version("1.1", digit=IMPORTANT_DIGITS) 92 | cm.decompress() 93 | cm.compress(important_digits=IMPORTANT_DIGITS, translate=translate) 94 | outfile = output_dir / "metadata.city.json" 95 | with outfile.open("w") as fo: 96 | fo.write(cm.cityjson_for_features()) 97 | print(f"Written {outfile}") 98 | del cm, outfile 99 | 100 | # --- Split to features 101 | def file_to_feature_files(filepath: Path, out_dir: Path): 102 | with filepath.open(mode='r', encoding='utf-8-sig') as f: 103 | cm = cityjson.reader(file=f, ignore_duplicate_keys=False) 104 | cm.upgrade_version("1.1", digit=IMPORTANT_DIGITS) 105 | cm.decompress() 106 | cm.compress(important_digits=IMPORTANT_DIGITS, translate=translate) 107 | 108 | fail = [] 109 | # e.g: 'gb2' in /home/cjio/gb2.city.json 110 | old_filename = filepath.name.replace("".join(filepath.suffixes), "") 111 | # e.g: '/home/cjio/gb2' in /home/cjio/gb2.city.json 112 | filedir = out_dir / old_filename 113 | filedir.mkdir(exist_ok=True) 114 | for feature in cm.generate_features(): 115 | feature_id = feature.j['id'] 116 | new_filename = f"{feature_id}.city.jsonl" 117 | filepath = filedir / new_filename 118 | try: 119 | with open(filepath, "w") as fout: 120 | fout.write(json.dumps(feature.j, separators=(',', ':'))) 121 | except IOError as e: 122 | print(f"Invalid output file: {filepath}\n{e}") 123 | fail.append(feature_id) 124 | except BaseException as e: 125 | print(e) 126 | fail.append(feature_id) 127 | return fail 128 | 129 | 130 | failed = [] 131 | futures = [] 132 | with ProcessPoolExecutor(max_workers=max_workers) as executor: 133 | for cj_path in cityjson_path_list: 134 | futures.append(executor.submit(file_to_feature_files, cj_path, output_dir)) 135 | for i, future in enumerate(as_completed(futures)): 136 | failed.extend(future.result()) 137 | del i, future, futures, executor 138 | 139 | print(f"Failed to export the CityObjects: {failed}") 140 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Balázs Dukai, Ravi Peters 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | use std::path::{Path, PathBuf}; 15 | 16 | use clap::Parser; 17 | 18 | #[derive(Parser, Debug)] 19 | #[command(author, version, about)] 20 | pub struct Cli { 21 | /// Main CityJSON file (.city.json), containing the coordinate reference system and 22 | /// transformation properties. 23 | #[arg(short, long, value_parser = existing_canonical_path)] 24 | pub metadata: PathBuf, 25 | /// Directory of CityJSONFeatures (.city.jsonl). The directory and all its 26 | /// subdirectories are searched recursively for feature files. 27 | #[arg(short, long, value_parser = existing_canonical_path)] 28 | pub features: PathBuf, 29 | /// Directory for the output. 30 | #[arg(short, long)] 31 | pub output: PathBuf, 32 | // /// Output format. 33 | // #[arg(long, value_enum)] 34 | // pub format: crate::Formats, 35 | /// The CityObject type to use for the 3D Tiles 36 | /// (https://www.cityjson.org/specs/1.1.3/#the-different-city-objects). 37 | /// You can specify it multiple times. 38 | #[arg(long, value_enum)] 39 | pub object_type: Option>, 40 | /// The CityObject attribute name and value type to include as feature attribute when the 41 | /// output is 3D Tiles. Format: : eg: 'name1:string'. 42 | /// Possible value types are, 'bool', 'int', 'float', 'string'. 43 | /// You can specify it multiple times. 44 | #[arg(long)] 45 | pub object_attribute: Option>, 46 | /// The CityObject attribute 47 | /// The metadata class to assign to the property table when the output is 48 | /// 3D Tiles (https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_structural_metadata#class). 49 | #[arg(long = "3dtiles-metadata-class")] 50 | pub cesium3dtiles_metadata_class: Option, 51 | /// Create implicit tiling when the output format is 3D Tiles (https://docs.ogc.org/cs/22-025r4/22-025r4.html#toc31). 52 | /// By default, explicit tiling is created for the 3D Tiles output. 53 | #[arg(long = "3dtiles-implicit")] 54 | pub cesium3dtiles_implicit: bool, 55 | /// Generate and write the Tileset only, without exporting the glTF tiles, when the output format is 3D Tiles (https://docs.ogc.org/cs/22-025r4/22-025r4.html#toc31). 56 | #[arg(long = "3dtiles-tileset-only")] 57 | pub cesium3dtiles_tileset_only: bool, 58 | /// Use the tile boundingVolume as the content boundingVolume, instead of calculating the content boundingVolume from the data. 59 | #[arg(long = "3dtiles-content-bv-from-tile")] 60 | pub cesium3dtiles_content_bv_from_tile: bool, 61 | /// Add the boundingVolume of the content for the the tiles that have content. 62 | #[arg(long = "3dtiles-content-add-bv")] 63 | pub cesium3dtiles_content_add_bv: bool, 64 | /// Set the geometric error (see 3D Tiles specification) on the parent nodes of leafs. This controls at what 65 | /// camera distance leaf nodes become visible. Higher values make content visible earlier when zooming in. 66 | #[arg(long, short = 'e', default_value = "12")] 67 | pub geometric_error_above_leaf: Option, 68 | /// Set the 2D cell size for the grid that is used for constructing the quadtree. 69 | /// In input units (eg. meters). Note that the cell size will be adjusted so that it is 70 | /// possible to construct a tightly fit square, containing 4^n cells. The final cell size will 71 | /// larger than this value. 72 | #[arg(long, default_value = "250")] 73 | pub grid_cellsize: Option, 74 | /// Generate the quadtree directly from a grid.tsv file, skipping the extent computation and feature indexing. A grid.tsv file is created with the --grid-export option. Used for debugging. 75 | #[arg(long)] 76 | pub grid_file: Option, 77 | /// Limit the minimum z coordinate for the bounding box that is computed from the 78 | /// features. Useful if the features contain errors with extremely small z 79 | /// coordinates. In input units (eg. meters). 80 | #[arg(long)] 81 | pub grid_minz: Option, 82 | /// Limit the maximum z coordinate for the bounding box that is computed from the 83 | /// features. Useful if the features contain errors with extremely large z 84 | /// coordinates. In input units (eg. meters). 85 | #[arg(long)] 86 | pub grid_maxz: Option, 87 | /// Export the grid into .tsv files in the working 88 | /// directory. Used for debugging. 89 | #[arg(long)] 90 | pub grid_export: bool, 91 | /// Export the grid, and also the feature centroids into .tsv files in the working 92 | /// directory. Used for debugging. 93 | #[arg(long)] 94 | pub grid_export_features: bool, 95 | /// Load instances from this directory. 96 | /// In debug mode, tyler writes the generated world, quadtree etc. instances to .bincode files, which later can be used for debugging. 97 | /// When this argument is specified, tyler will load the instances from the .bincode files that are available in the directory. 98 | #[arg(long, value_parser = existing_canonical_path)] 99 | pub debug_load_data: Option, 100 | /// The maximum number of vertices in a leaf of the quadtree. 101 | #[arg(long, default_value = "42000")] 102 | pub qtree_capacity: Option, 103 | /// Path to the geoflow executable for clipping and exporting the gltf files. 104 | #[arg(long, value_parser = existing_path)] 105 | pub exe_geof: Option, 106 | /// Maximum error that is allowed in mesh simplification to reduce the number of vertices. Value should be a float that represents that maximum allowed error in meters. Ignored for building object types. 107 | #[arg(long, default_value = "1.0")] 108 | pub simplification_max_error: Option, 109 | /// Compute smooth vertex normals. 110 | #[arg(long)] 111 | pub smooth_normals: bool, 112 | /// Wait for the tile conversion process to finish, or terminate it if it is not finished after the provided number of seconds. 113 | #[arg(long)] 114 | pub timeout: Option, 115 | /// LoD to use in output for Building features 116 | #[arg(long)] 117 | pub lod_building: Option, 118 | /// LoD to use in output for building_part features 119 | #[arg(long)] 120 | pub lod_building_part: Option, 121 | /// LoD to use in output for building_installation features 122 | #[arg(long)] 123 | pub lod_building_installation: Option, 124 | /// LoD to use in output for tin_relief features 125 | #[arg(long)] 126 | pub lod_tin_relief: Option, 127 | /// LoD to use in output for road features 128 | #[arg(long)] 129 | pub lod_road: Option, 130 | /// LoD to use in output for railway features 131 | #[arg(long)] 132 | pub lod_railway: Option, 133 | /// LoD to use in output for transport_square features 134 | #[arg(long)] 135 | pub lod_transport_square: Option, 136 | /// LoD to use in output for water_body features 137 | #[arg(long)] 138 | pub lod_water_body: Option, 139 | /// LoD to use in output for plant_cover features 140 | #[arg(long)] 141 | pub lod_plant_cover: Option, 142 | /// LoD to use in output for solitary_vegetation_object features 143 | #[arg(long)] 144 | pub lod_solitary_vegetation_object: Option, 145 | /// LoD to use in output for land_use features 146 | #[arg(long)] 147 | pub lod_land_use: Option, 148 | /// LoD to use in output for city_furniture features 149 | #[arg(long)] 150 | pub lod_city_furniture: Option, 151 | /// LoD to use in output for bridge features 152 | #[arg(long)] 153 | pub lod_bridge: Option, 154 | /// LoD to use in output for bridge_part features 155 | #[arg(long)] 156 | pub lod_bridge_part: Option, 157 | /// LoD to use in output for bridge_installation features 158 | #[arg(long)] 159 | pub lod_bridge_installation: Option, 160 | /// LoD to use in output for bridge_construction_element features 161 | #[arg(long)] 162 | pub lod_bridge_construction_element: Option, 163 | /// LoD to use in output for tunnel features 164 | #[arg(long)] 165 | pub lod_tunnel: Option, 166 | /// LoD to use in output for tunnel_part features 167 | #[arg(long)] 168 | pub lod_tunnel_part: Option, 169 | /// LoD to use in output for tunnel_installation features 170 | #[arg(long)] 171 | pub lod_tunnel_installation: Option, 172 | /// LoD to use in output for lod_generic_city_object features 173 | #[arg(long)] 174 | pub lod_generic_city_object: Option, 175 | /// Color for Building features specified as a hex rgb-color value, eg. #FF0000 is red. 176 | #[arg(long, value_parser = hex_color)] 177 | pub color_building: Option, 178 | /// Color for BuildingPart features specified as a hex rgb-color value, eg. #FF0000 is red. 179 | #[arg(long, value_parser = hex_color)] 180 | pub color_building_part: Option, 181 | /// Color for BuildingInstallation features specified as a hex rgb-color value, eg. #FF0000 is red. 182 | #[arg(long, value_parser = hex_color)] 183 | pub color_building_installation: Option, 184 | /// Color for TINRelief features specified as a hex rgb-color value, eg. #FF0000 is red. 185 | #[arg(long, value_parser = hex_color)] 186 | pub color_tin_relief: Option, 187 | /// Color for Road features specified as a hex rgb-color value, eg. #FF0000 is red. 188 | #[arg(long, value_parser = hex_color)] 189 | pub color_road: Option, 190 | /// Color for Railway features specified as a hex rgb-color value, eg. #FF0000 is red. 191 | #[arg(long, value_parser = hex_color)] 192 | pub color_railway: Option, 193 | /// Color for TransportSquare features specified as a hex rgb-color value, eg. #FF0000 is red. 194 | #[arg(long, value_parser = hex_color)] 195 | pub color_transport_square: Option, 196 | /// Color for WaterBody features specified as a hex rgb-color value, eg. #FF0000 is red. 197 | #[arg(long, value_parser = hex_color)] 198 | pub color_water_body: Option, 199 | /// Color for PlantCover features specified as a hex rgb-color value, eg. #FF0000 is red. 200 | #[arg(long, value_parser = hex_color)] 201 | pub color_plant_cover: Option, 202 | /// Color for SolitaryVegetationObject features specified as a hex rgb-color value, eg. #FF0000 is red. 203 | #[arg(long, value_parser = hex_color)] 204 | pub color_solitary_vegetation_object: Option, 205 | /// Color for LandUse features specified as a hex rgb-color value, eg. #FF0000 is red. 206 | #[arg(long, value_parser = hex_color)] 207 | pub color_land_use: Option, 208 | /// Color for CityFurniture features specified as a hex rgb-color value, eg. #FF0000 is red. 209 | #[arg(long, value_parser = hex_color)] 210 | pub color_city_furniture: Option, 211 | /// Color for Bridge features specified as a hex rgb-color value, eg. #FF0000 is red. 212 | #[arg(long, value_parser = hex_color)] 213 | pub color_bridge: Option, 214 | /// Color for BridgePart features specified as a hex rgb-color value, eg. #FF0000 is red. 215 | #[arg(long, value_parser = hex_color)] 216 | pub color_bridge_part: Option, 217 | /// Color for BridgeInstallation features specified as a hex rgb-color value, eg. #FF0000 is red. 218 | #[arg(long, value_parser = hex_color)] 219 | pub color_bridge_installation: Option, 220 | /// Color for BridgeConstructionElement features specified as a hex rgb-color value, eg. #FF0000 is red. 221 | #[arg(long, value_parser = hex_color)] 222 | pub color_bridge_construction_element: Option, 223 | /// Color for Tunnel features specified as a hex rgb-color value, eg. #FF0000 is red. 224 | #[arg(long, value_parser = hex_color)] 225 | pub color_tunnel: Option, 226 | /// Color for TunnelPart features specified as a hex rgb-color value, eg. #FF0000 is red. 227 | #[arg(long, value_parser = hex_color)] 228 | pub color_tunnel_part: Option, 229 | /// Color for TunnelInstallation features specified as a hex rgb-color value, eg. #FF0000 is red. 230 | #[arg(long, value_parser = hex_color)] 231 | pub color_tunnel_installation: Option, 232 | /// Color for GenericCityObject features specified as a hex rgb-color value, eg. #FF0000 is red. 233 | #[arg(long, value_parser = hex_color)] 234 | pub color_generic_city_object: Option, 235 | // The number of levels to export as content from the quadtree. 236 | // Counted from the leaves. 237 | // #[arg(long, default_value = "0")] 238 | // pub qtree_export_levels: Option, 239 | // /// The criteria to check for the quadtree leaf capacity. 240 | // #[arg(long, value_enum, default_value = "vertices")] 241 | // pub qtree_criteria: Option, 242 | // /// Path to the python interpreter (>=3.8) to use for generating CityJSON tiles. 243 | // /// The interpreter must have a recent cjio (https://github.com/cityjson/cjio) 244 | // /// installed. 245 | // #[arg(long, value_parser = existing_path)] 246 | // pub exe_python: Option, 247 | } 248 | 249 | fn existing_canonical_path(s: &str) -> Result { 250 | if let Ok(c) = Path::new(s).canonicalize() { 251 | if c.exists() { 252 | Ok(c) 253 | } else { 254 | Err(format!("path {:?} does not exist", &c)) 255 | } 256 | } else { 257 | Err(format!("could not resolve the path {:?}", s)) 258 | } 259 | } 260 | 261 | /// We don't want to canonicalize paths to executables, especially a python exe from a 262 | /// virtualenv, because the symlink would get resolved and we would end up with a path 263 | /// to the python interpreter that was used for creating the virtualenv, and not the 264 | /// interpreter that links to the virtualenv. 265 | fn existing_path(s: &str) -> Result { 266 | let p = Path::new(s).to_path_buf(); 267 | if p.exists() { 268 | Ok(p) 269 | } else { 270 | Err(format!("path {:?} does not exist", &p)) 271 | } 272 | } 273 | 274 | /// Checks is `s` constains a 6 digit hexadecimal value preceded by a '#', eg. #FF0000 275 | fn hex_color(s: &str) -> Result { 276 | if s.len() != 7 || !s.starts_with('#') { 277 | return Err(String::from( 278 | "Input must be a 6-digit hexadecimal value preceded by a '#'", 279 | )); 280 | } 281 | let hex_digits = &s[1..]; 282 | if !hex_digits.chars().all(|c| c.is_ascii_hexdigit()) { 283 | return Err(String::from( 284 | "Input must be a 6-digit hexadecimal value preceded by a '#'", 285 | )); 286 | } 287 | Ok(String::from(s)) 288 | } 289 | 290 | #[cfg(test)] 291 | mod tests { 292 | use super::Cli; 293 | use clap::{CommandFactory, Parser}; 294 | 295 | fn required_args() -> Vec<&'static str> { 296 | vec![ 297 | "tyler", 298 | "-m", 299 | "metadata.city.json", 300 | "-f", 301 | env!("CARGO_MANIFEST_DIR"), 302 | "-o", 303 | env!("CARGO_MANIFEST_DIR"), 304 | "--format", 305 | "3dtiles", 306 | ] 307 | } 308 | 309 | #[test] 310 | fn verify_cli() { 311 | Cli::command().debug_assert() 312 | } 313 | 314 | /// Can we pass multiple CityObject types? 315 | #[test] 316 | fn verify_object_types() { 317 | let mut types: Vec<&'static str> = 318 | vec!["--object-type", "Building", "--object-type", "PlantCover"]; 319 | let mut args = required_args(); 320 | args.append(&mut types); 321 | let cli = Cli::try_parse_from(args).unwrap(); 322 | let otypes = &cli.object_type.unwrap(); 323 | assert!(otypes.contains(&crate::parser::CityObjectType::Building)); 324 | assert!(otypes.contains(&crate::parser::CityObjectType::PlantCover)); 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /src/proj.rs: -------------------------------------------------------------------------------- 1 | //! Proj.convert() function adapted from the 2 | //! [proj](https://github.com/georust/proj/blob/main/src/proj.rs) crate to transform xyz 3 | //! coordinates instead of only xy. 4 | // Copyright 2017 The GeoRust Project Developers https://github.com/georust/proj/blob/main/LICENSE-APACHE 5 | // Copyright 2023 Balázs Dukai, Ravi Peters 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | use libc::c_int; 20 | use libc::{c_char, c_double}; 21 | use num_traits::Float; 22 | use proj_sys::{ 23 | proj_area_create, proj_area_set_bbox, proj_context_create, proj_context_errno, 24 | proj_create_crs_to_crs, proj_destroy, proj_errno_string, proj_normalize_for_visualization, 25 | proj_trans, PJconsts, PJ_AREA, PJ_CONTEXT, PJ_COORD, PJ_DIRECTION_PJ_FWD, PJ_XYZT, 26 | }; 27 | use std::{fmt::Debug, str}; 28 | 29 | use proj_sys::{proj_errno, proj_errno_reset}; 30 | 31 | use std::ffi::{CStr, CString, NulError}; 32 | use thiserror::Error; 33 | 34 | pub trait CoordinateType: Float + Copy + PartialOrd + Debug {} 35 | 36 | impl CoordinateType for T {} 37 | 38 | /// Called by new_known_crs and proj_known_crs 39 | fn transform_epsg( 40 | ctx: *mut PJ_CONTEXT, 41 | from: &str, 42 | to: &str, 43 | area: Option, 44 | ) -> Result { 45 | let from_c = CString::new(from).map_err(ProjCreateError::ArgumentNulError)?; 46 | let to_c = CString::new(to).map_err(ProjCreateError::ArgumentNulError)?; 47 | let proj_area = unsafe { proj_area_create() }; 48 | area_set_bbox(proj_area, area); 49 | let ptr = result_from_create(ctx, unsafe { 50 | proj_create_crs_to_crs(ctx, from_c.as_ptr(), to_c.as_ptr(), proj_area) 51 | }) 52 | .map_err(|e| ProjCreateError::ProjError(e.message(ctx)))?; 53 | // Normalise input and output order to Lon, Lat / Easting Northing by inserting 54 | // An axis swap operation if necessary 55 | let normalised = unsafe { 56 | let normalised = proj_normalize_for_visualization(ctx, ptr); 57 | // deallocate stale PJ pointer 58 | proj_destroy(ptr); 59 | normalised 60 | }; 61 | Ok(Proj { 62 | c_proj: normalised, 63 | ctx, 64 | area: Some(proj_area), 65 | }) 66 | } 67 | 68 | /// Construct a `Result` from the result of a `proj_create*` call. 69 | fn result_from_create(context: *mut PJ_CONTEXT, ptr: *mut T) -> Result<*mut T, Errno> { 70 | if ptr.is_null() { 71 | Err(Errno(unsafe { proj_context_errno(context) })) 72 | } else { 73 | Ok(ptr) 74 | } 75 | } 76 | 77 | #[derive(Copy, Clone, Debug)] 78 | pub struct Area { 79 | pub north: f64, 80 | pub south: f64, 81 | pub east: f64, 82 | pub west: f64, 83 | } 84 | 85 | fn area_set_bbox(parea: *mut PJ_AREA, new_area: Option) { 86 | // if a bounding box has been passed, modify the proj area object 87 | if let Some(narea) = new_area { 88 | unsafe { 89 | proj_area_set_bbox(parea, narea.west, narea.south, narea.east, narea.north); 90 | } 91 | } 92 | } 93 | 94 | pub trait Coord 95 | where 96 | T: CoordinateType, 97 | { 98 | fn x(&self) -> T; 99 | fn y(&self) -> T; 100 | fn z(&self) -> T; 101 | fn from_xyz(x: T, y: T, z: T) -> Self; 102 | } 103 | 104 | impl Coord for (T, T, T) { 105 | fn x(&self) -> T { 106 | self.0 107 | } 108 | fn y(&self) -> T { 109 | self.1 110 | } 111 | fn z(&self) -> T { 112 | self.2 113 | } 114 | fn from_xyz(x: T, y: T, z: T) -> Self { 115 | (x, y, z) 116 | } 117 | } 118 | 119 | #[allow(dead_code)] 120 | pub struct Proj { 121 | c_proj: *mut PJconsts, 122 | ctx: *mut PJ_CONTEXT, 123 | area: Option<*mut PJ_AREA>, 124 | } 125 | 126 | impl Proj { 127 | pub fn new_known_crs( 128 | from: &str, 129 | to: &str, 130 | area: Option, 131 | ) -> Result { 132 | let ctx = unsafe { proj_context_create() }; 133 | transform_epsg(ctx, from, to, area) 134 | } 135 | 136 | pub fn convert(&self, point: C) -> Result 137 | where 138 | C: Coord, 139 | F: CoordinateType, 140 | { 141 | let c_x: c_double = point.x().to_f64().ok_or(ProjError::FloatConversion)?; 142 | let c_y: c_double = point.y().to_f64().ok_or(ProjError::FloatConversion)?; 143 | let c_z: c_double = point.z().to_f64().ok_or(ProjError::FloatConversion)?; 144 | let new_x; 145 | let new_y; 146 | let new_z; 147 | let err; 148 | 149 | // This doesn't seem strictly correct, but if we set PJ_XY or PJ_LP here, the 150 | // other two values remain uninitialized and we can't be sure that libproj 151 | // doesn't try to read them. proj_trans_generic does the same thing. 152 | let xyzt = PJ_XYZT { 153 | x: c_x, 154 | y: c_y, 155 | z: c_z, 156 | t: f64::INFINITY, 157 | }; 158 | unsafe { 159 | proj_errno_reset(self.c_proj); 160 | let trans = proj_trans(self.c_proj, PJ_DIRECTION_PJ_FWD, PJ_COORD { xyzt }); 161 | new_x = trans.xyz.x; 162 | new_y = trans.xyz.y; 163 | new_z = trans.xyz.z; 164 | err = proj_errno(self.c_proj); 165 | } 166 | if err == 0 { 167 | Ok(C::from_xyz( 168 | F::from(new_x).ok_or(ProjError::FloatConversion)?, 169 | F::from(new_y).ok_or(ProjError::FloatConversion)?, 170 | F::from(new_z).ok_or(ProjError::FloatConversion)?, 171 | )) 172 | } else { 173 | Err(ProjError::Conversion(error_message(err)?)) 174 | } 175 | } 176 | } 177 | 178 | /// Errors originating in PROJ which can occur during projection and conversion 179 | #[derive(Error, Debug)] 180 | pub enum ProjError { 181 | /// A conversion error 182 | #[error("The conversion failed with the following error: {0}")] 183 | Conversion(String), 184 | /// An error that occurs when a path string originating in PROJ can't be converted to a CString 185 | #[error("Couldn't create a raw pointer from the string")] 186 | Creation(#[from] NulError), 187 | #[error("Couldn't convert bytes from PROJ to UTF-8")] 188 | Utf8Error(#[from] str::Utf8Error), 189 | #[error("Couldn't convert number to f64")] 190 | FloatConversion, 191 | } 192 | 193 | #[derive(Error, Debug)] 194 | pub enum ProjCreateError { 195 | #[error("A nul byte was found in the PROJ string definition or CRS argument: {0}")] 196 | ArgumentNulError(NulError), 197 | #[error("The underlying PROJ call failed: {0}")] 198 | ProjError(String), 199 | } 200 | 201 | pub(crate) struct Errno(pub c_int); 202 | 203 | impl Errno { 204 | /// Return the error message associated with the error number. 205 | pub fn message(&self, context: *mut PJ_CONTEXT) -> String { 206 | let ptr = unsafe { proj_sys::proj_context_errno_string(context, self.0) }; 207 | if ptr.is_null() { 208 | panic!("PROJ did not supply an error") 209 | } else { 210 | unsafe { _string(ptr).expect("PROJ provided an invalid error string") } 211 | } 212 | } 213 | } 214 | 215 | /// Easily get a String from the external library 216 | pub(crate) unsafe fn _string(raw_ptr: *const c_char) -> Result { 217 | assert!(!raw_ptr.is_null()); 218 | let c_str = CStr::from_ptr(raw_ptr); 219 | Ok(str::from_utf8(c_str.to_bytes())?.to_string()) 220 | } 221 | 222 | /// Look up an error message using the error code 223 | fn error_message(code: c_int) -> Result { 224 | unsafe { 225 | let rv = proj_errno_string(code); 226 | _string(rv) 227 | } 228 | } 229 | 230 | #[cfg(test)] 231 | mod tests { 232 | use super::*; 233 | 234 | #[test] 235 | fn test_convert() { 236 | let crs_from = "EPSG:7415"; 237 | // Because we have a boundingVolume.box. For a boundingVolume.region we need 4979. 238 | let crs_to = "EPSG:4978"; 239 | let transformer = Proj::new_known_crs(crs_from, crs_to, None).unwrap(); 240 | let result = transformer.convert((85285.279, 446606.813, 10.0)).unwrap(); 241 | println!("{:?}", result); 242 | // assert_relative_eq!(result.x() as f64, 3923215.044, epsilon = 1e-2); 243 | // assert_relative_eq!(result.y() as f64, 299940.760, epsilon = 1e-2); 244 | // assert_relative_eq!(result.z() as f64, 5003047.651, epsilon = 1e-2); 245 | 246 | // [0] = {f64} 85185.2799868164 247 | // [1] = {f64} 446506.81397216802 248 | // [2] = {f64} -15.333460330963135 249 | // [3] = {f64} 85385.2799868164 250 | // [4] = {f64} 446706.81397216802 251 | // [5] = {f64} 62.881539669036869 252 | 253 | // [0] = {f64} 3923286.6789069851 254 | // [1] = {f64} 299847.35238179943 255 | // [2] = {f64} 5002965.2671014387 256 | // [3] = {f64} 3923160.3138962006 257 | // [4] = {f64} 300035.46416343335 258 | // [5] = {f64} 5003151.7442537257 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /tyler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3DGI/tyler/f4685eda1f6f5a51a2c5304209f9bef05ad5249d/tyler.png --------------------------------------------------------------------------------