CityParquet benchmark figures
Self-contained paper figures. The format comparison's colours encode factors against CityGML (CityGML's value ÷ the format's; higher is better); the other families state their own baselines. Values remain printed.
Times: median of 25 warm runs per cell; spread: interquartile range (q1–q3), min–max recorded.
File size on disk

Zurich — read time

Conditions
- zurich_building_lod2: attribute filter class = 'BB01' (38,601 matched, share 19.4 %, hand-picked); windows bbox-1pct achieved 1 %, bbox-5pct achieved 5 %, bbox-25pct achieved 25 %
Zurich — read peak memory

Conditions
- zurich_building_lod2: attribute filter class = 'BB01' (38,601 matched, share 19.4 %, hand-picked); windows bbox-1pct achieved 1 %, bbox-5pct achieved 5 %, bbox-25pct achieved 25 %
Tokyo (Chiyoda) — read time

Conditions
- tokyo: attribute filter usage = '401' (12,348 matched, share 24.7 %, hand-picked); windows bbox-1pct achieved 1 %, bbox-5pct achieved 5 %, bbox-25pct achieved 25 %
Tokyo (Chiyoda) — read peak memory

Conditions
- tokyo: attribute filter usage = '401' (12,348 matched, share 24.7 %, hand-picked); windows bbox-1pct achieved 1 %, bbox-5pct achieved 5 %, bbox-25pct achieved 25 %
Montréal — read time

Conditions
- montreal: attribute filter measuredHeight >= 15.543 (7,858 matched, share 25 %, hand-picked); windows bbox-1pct achieved 1 %, bbox-5pct achieved 5 %, bbox-25pct achieved 25 %
Montréal — read peak memory

Conditions
- montreal: attribute filter measuredHeight >= 15.543 (7,858 matched, share 25 %, hand-picked); windows bbox-1pct achieved 1 %, bbox-5pct achieved 5 %, bbox-25pct achieved 25 %
New York — read time

Conditions
- nyc_da13_buildings: attribute filter BIN = '1000000' (173 matched, share 0.728 %, hand-picked); windows bbox-1pct achieved 1 %, bbox-5pct achieved 5 %, bbox-25pct achieved 25 %
New York — read peak memory

Conditions
- nyc_da13_buildings: attribute filter BIN = '1000000' (173 matched, share 0.728 %, hand-picked); windows bbox-1pct achieved 1 %, bbox-5pct achieved 5 %, bbox-25pct achieved 25 %
Vienna — read time

Conditions
- vienna_102081: attribute filter roofType = 'FLACHDACH' (600 matched, share 45.4 %, hand-picked); windows bbox-1pct achieved 0.983 %, bbox-5pct achieved 5.07 %, bbox-25pct achieved 25 %
Vienna — read peak memory

Conditions
- vienna_102081: attribute filter roofType = 'FLACHDACH' (600 matched, share 45.4 %, hand-picked); windows bbox-1pct achieved 0.983 %, bbox-5pct achieved 5.07 %, bbox-25pct achieved 25 %
Rotterdam — read time

Conditions
- rotterdam_delfshaven: attribute filter TerrainHeight >= 2.45 (217 matched, share 25.4 %, hand-picked); windows bbox-1pct achieved 1.06 %, bbox-5pct achieved 5.04 %, bbox-25pct achieved 25 %
Rotterdam — read peak memory

Conditions
- rotterdam_delfshaven: attribute filter TerrainHeight >= 2.45 (217 matched, share 25.4 %, hand-picked); windows bbox-1pct achieved 1.06 %, bbox-5pct achieved 5.04 %, bbox-25pct achieved 25 %
3DBAG (Netherlands) — read time
Not rendered: result family absent.
3DBAG (Netherlands) — read peak memory
Not rendered: result family absent.
Bloom-filter configuration

Database comparison

Format reads over HTTP (network family): not measured
Not measured: no network results exist for this summary.
Size factors against CityGML
Every factor is CityGML's value divided by the format's, so a larger factor is better (faster, leaner or smaller than CityGML). An empty factor is unavailable: the CityGML or the format cell is missing, skipped or failed; the note says which.
| dataset | title | city_objects | citygml_bytes | cityjson_bytes | cityjson_factor | cityjsonseq_bytes | cityjsonseq_factor | flatcitybuf_bytes | flatcitybuf_factor | cityparquet_bytes | cityparquet_factor |
|---|
| zurich_building_lod2 | Zurich | 198699 | 1417350755 | 292500409 | 4.8456 | 259121439 | 5.4698 | 206331130 | 6.8693 | 71855768 | 19.7249 |
| tokyo | Tokyo (Chiyoda) | 49915 | 1484995928 | 315968009 | 4.6998 | 303886615 | 4.8867 | 265359818 | 5.5962 | 80184073 | 18.5198 |
| montreal | Montréal | 31415 | 3258190086 | 498371022 | 6.5377 | 372320945 | 8.7510 | 366240400 | 8.8963 | 158972459 | 20.4953 |
| nyc_da13_buildings | New York | 23777 | 568258538 | 110083137 | 5.1621 | 100087976 | 5.6776 | 85047072 | 6.6817 | 17245066 | 32.9519 |
| vienna_102081 | Vienna | 1322 | 21064612 | 4731370 | 4.4521 | 4449108 | 4.7346 | 3619146 | 5.8203 | 1397524 | 15.0728 |
| rotterdam_delfshaven | Rotterdam | 853 | 14046392 | 2731804 | 5.1418 | 2822644 | 4.9763 | 3017520 | 4.6549 | 714089 | 19.6704 |
Best and worst dataset by CityParquet's size factor
Every factor is CityGML's value divided by the format's, so a larger factor is better (faster, leaner or smaller than CityGML). An empty factor is unavailable: the CityGML or the format cell is missing, skipped or failed; the note says which.
| rank | dataset | title | city_objects | cityparquet_factor |
|---|
| best | nyc_da13_buildings | New York | 23777 | 32.9519 |
| worst | vienna_102081 | Vienna | 1322 | 15.0728 |
Query factors against CityGML
Every factor is CityGML's value divided by the format's, so a larger factor is better (faster, leaner or smaller than CityGML). An empty factor is unavailable: the CityGML or the format cell is missing, skipped or failed; the note says which.
| dataset | query | format | format_id | statistic | time_value_s | time_lo_s | time_hi_s | time_min_s | time_max_s | peak_rss_bytes | citygml_time_s | citygml_peak_rss_bytes | time_factor_vs_citygml | rss_factor_vs_citygml | note |
|---|
| zurich_building_lod2 | full-read | CityGML | citygml | median | 7.958359 | 7.928660 | 8.022294 | 7.897244 | 8.418232 | 30572544 | 7.958359 | 30572544 | 1.0000 | 1.0000 | |
| zurich_building_lod2 | full-read | CityJSON | cityjson | median | 1.824947 | 1.801076 | 1.876504 | 1.766495 | 2.721176 | 4160880640 | 7.958359 | 30572544 | 4.3609 | 0.0073 | |
| zurich_building_lod2 | full-read | CityJSONSeq | cityjsonseq | median | 1.229377 | 1.218837 | 1.259297 | 1.197104 | 1.943921 | 546701312 | 7.958359 | 30572544 | 6.4735 | 0.0559 | |
| zurich_building_lod2 | full-read | FlatCityBuf | flatcitybuf | median | 0.155796 | 0.151725 | 0.157668 | 0.149349 | 0.185556 | 7782400 | 7.958359 | 30572544 | 51.0819 | 3.9284 | |
| zurich_building_lod2 | full-read | CityParquet | cityparquet | median | 0.559127 | 0.550057 | 0.561508 | 0.543484 | 0.593637 | 88834048 | 7.958359 | 30572544 | 14.2335 | 0.3442 | |
| zurich_building_lod2 | count | CityGML | citygml | median | 7.837472 | 7.808661 | 7.873604 | 7.782349 | 8.042036 | 30638080 | 7.837472 | 30638080 | 1.0000 | 1.0000 | |
| zurich_building_lod2 | count | CityJSON | cityjson | median | 1.523112 | 1.513527 | 1.532582 | 1.466929 | 1.620765 | 4160831488 | 7.837472 | 30638080 | 5.1457 | 0.0074 | |
| zurich_building_lod2 | count | CityJSONSeq | cityjsonseq | median | 1.154294 | 1.132898 | 1.179080 | 1.122420 | 1.381178 | 545964032 | 7.837472 | 30638080 | 6.7898 | 0.0561 | |
| zurich_building_lod2 | count | FlatCityBuf | flatcitybuf | median | 0.000026 | 0.000024 | 0.000029 | 0.000023 | 0.000038 | 7290880 | 7.837472 | 30638080 | 301441.2308 | 4.2022 | |
| zurich_building_lod2 | count | CityParquet | cityparquet | median | 0.000259 | 0.000254 | 0.000266 | 0.000248 | 0.000349 | 9224192 | 7.837472 | 30638080 | 30260.5097 | 3.3215 | |
| zurich_building_lod2 | bbox-1pct | CityGML | citygml | median | 8.037762 | 7.967751 | 8.089324 | 7.884430 | 8.783372 | 30851072 | 8.037762 | 30851072 | 1.0000 | 1.0000 | |
| zurich_building_lod2 | bbox-1pct | CityJSON | cityjson | median | 1.978653 | 1.945258 | 2.069801 | 1.907914 | 2.488730 | 4181573632 | 8.037762 | 30851072 | 4.0622 | 0.0074 | |
| zurich_building_lod2 | bbox-1pct | CityJSONSeq | cityjsonseq | median | 1.230296 | 1.223031 | 1.253435 | 1.212400 | 1.422244 | 546144256 | 8.037762 | 30851072 | 6.5332 | 0.0565 | |
| zurich_building_lod2 | bbox-1pct | FlatCityBuf | flatcitybuf | median | 0.001910 | 0.001859 | 0.001960 | 0.001791 | 0.002541 | 8142848 | 8.037762 | 30851072 | 4208.2524 | 3.7887 | |
| zurich_building_lod2 | bbox-1pct | CityParquet | cityparquet | median | 0.035007 | 0.034575 | 0.035457 | 0.034194 | 0.038889 | 25870336 | 8.037762 | 30851072 | 229.6044 | 1.1925 | |
| zurich_building_lod2 | bbox-5pct | CityGML | citygml | median | 8.036732 | 7.985248 | 8.191130 | 7.930020 | 8.487619 | 30949376 | 8.036732 | 30949376 | 1.0000 | 1.0000 | |
| zurich_building_lod2 | bbox-5pct | CityJSON | cityjson | median | 1.978820 | 1.941995 | 1.998231 | 1.907329 | 2.478386 | 4181934080 | 8.036732 | 30949376 | 4.0614 | 0.0074 | |
| zurich_building_lod2 | bbox-5pct | CityJSONSeq | cityjsonseq | median | 1.224431 | 1.216281 | 1.235414 | 1.208277 | 1.427184 | 546013184 | 8.036732 | 30949376 | 6.5636 | 0.0567 | |
| zurich_building_lod2 | bbox-5pct | FlatCityBuf | flatcitybuf | median | 0.008921 | 0.008769 | 0.009148 | 0.008637 | 0.009815 | 8519680 | 8.036732 | 30949376 | 900.8779 | 3.6327 | |
| zurich_building_lod2 | bbox-5pct | CityParquet | cityparquet | median | 0.061319 | 0.060324 | 0.062298 | 0.058990 | 0.063426 | 39944192 | 8.036732 | 30949376 | 131.0643 | 0.7748 | |
| zurich_building_lod2 | bbox-25pct | CityGML | citygml | median | 7.964976 | 7.930503 | 8.039457 | 7.890968 | 8.373525 | 30605312 | 7.964976 | 30605312 | 1.0000 | 1.0000 | |
| zurich_building_lod2 | bbox-25pct | CityJSON | cityjson | median | 1.987771 | 1.962645 | 2.014939 | 1.935248 | 2.289362 | 4179771392 | 7.964976 | 30605312 | 4.0070 | 0.0073 | |
| zurich_building_lod2 | bbox-25pct | CityJSONSeq | cityjsonseq | median | 1.238668 | 1.231574 | 1.248715 | 1.218266 | 1.291172 | 543670272 | 7.964976 | 30605312 | 6.4303 | 0.0563 | |
| zurich_building_lod2 | bbox-25pct | FlatCityBuf | flatcitybuf | median | 0.045881 | 0.044113 | 0.047118 | 0.043097 | 0.049097 | 9846784 | 7.964976 | 30605312 | 173.6007 | 3.1082 | |
| zurich_building_lod2 | bbox-25pct | CityParquet | cityparquet | median | 0.113065 | 0.112393 | 0.114142 | 0.110315 | 0.129851 | 59162624 | 7.964976 | 30605312 | 70.4460 | 0.5173 | |
| zurich_building_lod2 | attr-filter | CityGML | citygml | median | 7.914707 | 7.848581 | 8.064739 | 7.767576 | 31.191037 | 30621696 | 7.914707 | 30621696 | 1.0000 | 1.0000 | |
| zurich_building_lod2 | attr-filter | CityJSON | cityjson | median | 1.544214 | 1.536014 | 1.558789 | 1.485658 | 1.597611 | 4160897024 | 7.914707 | 30621696 | 5.1254 | 0.0074 | |
| zurich_building_lod2 | attr-filter | CityJSONSeq | cityjsonseq | median | 1.139812 | 1.135362 | 1.147555 | 1.126329 | 1.204031 | 543293440 | 7.914707 | 30621696 | 6.9439 | 0.0564 | |
| zurich_building_lod2 | attr-filter | FlatCityBuf | flatcitybuf | median | 0.120423 | 0.116415 | 0.121266 | 0.114904 | 0.124993 | 14237696 | 7.914707 | 30621696 | 65.7242 | 2.1507 | |
| zurich_building_lod2 | attr-filter | CityParquet | cityparquet | median | 0.011878 | 0.011828 | 0.011942 | 0.011678 | 0.012142 | 19218432 | 7.914707 | 30621696 | 666.3333 | 1.5934 | |
| zurich_building_lod2 | attr-stats | CityGML | citygml | median | 7.784363 | 7.695191 | 8.061006 | 7.574663 | 18.198944 | 30867456 | 7.784363 | 30867456 | 1.0000 | 1.0000 | |
| zurich_building_lod2 | attr-stats | CityJSON | cityjson | median | 1.555532 | 1.547742 | 1.585556 | 1.524818 | 2.191082 | 4160831488 | 7.784363 | 30867456 | 5.0043 | 0.0074 | |
| zurich_building_lod2 | attr-stats | CityJSONSeq | cityjsonseq | median | 1.139906 | 1.132310 | 1.148312 | 1.125190 | 1.177414 | 545914880 | 7.784363 | 30867456 | 6.8290 | 0.0565 | |
| zurich_building_lod2 | attr-stats | FlatCityBuf | flatcitybuf | median | 0.126192 | 0.123560 | 0.130168 | 0.121211 | 0.133382 | 7749632 | 7.784363 | 30867456 | 61.6867 | 3.9831 | |
| zurich_building_lod2 | attr-stats | CityParquet | cityparquet | median | 0.001157 | 0.001074 | 0.001612 | 0.001027 | 0.003033 | 10452992 | 7.784363 | 30867456 | 6728.0579 | 2.9530 | |
| zurich_building_lod2 | id-10pct | CityGML | citygml | median | 0.610096 | 0.608808 | 0.612120 | 0.600251 | 0.625273 | 14090240 | 0.610096 | 14090240 | 1.0000 | 1.0000 | |
| zurich_building_lod2 | id-10pct | CityJSON | cityjson | median | 1.537513 | 1.526456 | 1.550865 | 1.477686 | 1.778799 | 4160765952 | 0.610096 | 14090240 | 0.3968 | 0.0034 | |
| zurich_building_lod2 | id-10pct | CityJSONSeq | cityjsonseq | median | 0.192011 | 0.185241 | 0.203215 | 0.180523 | 0.363316 | 541917184 | 0.610096 | 14090240 | 3.1774 | 0.0260 | |
| zurich_building_lod2 | id-10pct | FlatCityBuf | flatcitybuf | median | 0.033689 | 0.032995 | 0.034526 | 0.031404 | 0.061519 | 7798784 | 0.610096 | 14090240 | 18.1097 | 1.8067 | |
| zurich_building_lod2 | id-10pct | CityParquet | cityparquet | median | 0.009560 | 0.009376 | 0.009869 | 0.009153 | 0.026311 | 28147712 | 0.610096 | 14090240 | 63.8176 | 0.5006 | |
| zurich_building_lod2 | id-50pct | CityGML | citygml | median | 4.006234 | 3.984110 | 4.082504 | 3.967010 | 6.591521 | 25722880 | 4.006234 | 25722880 | 1.0000 | 1.0000 | |
| zurich_building_lod2 | id-50pct | CityJSON | cityjson | median | 1.548299 | 1.531755 | 1.573488 | 1.499314 | 1.662237 | 4160798720 | 4.006234 | 25722880 | 2.5875 | 0.0062 | |
| zurich_building_lod2 | id-50pct | CityJSONSeq | cityjsonseq | median | 0.630313 | 0.624256 | 0.639049 | 0.615629 | 0.681636 | 544833536 | 4.006234 | 25722880 | 6.3559 | 0.0472 | |
| zurich_building_lod2 | id-50pct | FlatCityBuf | flatcitybuf | median | 0.090502 | 0.087670 | 0.099912 | 0.084112 | 0.112629 | 7831552 | 4.006234 | 25722880 | 44.2668 | 3.2845 | |
| zurich_building_lod2 | id-50pct | CityParquet | cityparquet | median | 0.011910 | 0.011831 | 0.012011 | 0.011680 | 0.018810 | 27394048 | 4.006234 | 25722880 | 336.3757 | 0.9390 | |
| zurich_building_lod2 | id-90pct | CityGML | citygml | median | 7.370781 | 7.329127 | 7.414939 | 7.218378 | 7.765141 | 29720576 | 7.370781 | 29720576 | 1.0000 | 1.0000 | |
| zurich_building_lod2 | id-90pct | CityJSON | cityjson | median | 1.563205 | 1.542292 | 1.592264 | 1.515931 | 1.675300 | 4160831488 | 7.370781 | 29720576 | 4.7152 | 0.0071 | |
| zurich_building_lod2 | id-90pct | CityJSONSeq | cityjsonseq | median | 1.045651 | 1.043842 | 1.074079 | 1.037398 | 1.317598 | 545456128 | 7.370781 | 29720576 | 7.0490 | 0.0545 | |
| zurich_building_lod2 | id-90pct | FlatCityBuf | flatcitybuf | median | 0.117092 | 0.112073 | 0.125054 | 0.108304 | 0.150141 | 7815168 | 7.370781 | 29720576 | 62.9486 | 3.8029 | |
| zurich_building_lod2 | id-90pct | CityParquet | cityparquet | median | 0.009872 | 0.009661 | 0.010288 | 0.009442 | 0.021165 | 28819456 | 7.370781 | 29720576 | 746.6350 | 1.0313 | |
| zurich_building_lod2 | id-miss | CityGML | citygml | median | 7.943786 | 7.882796 | 8.023499 | 7.849479 | 8.237855 | 30638080 | 7.943786 | 30638080 | 1.0000 | 1.0000 | |
| zurich_building_lod2 | id-miss | CityJSON | cityjson | median | 1.545700 | 1.524923 | 1.579716 | 1.488742 | 2.426269 | 4160749568 | 7.943786 | 30638080 | 5.1393 | 0.0074 | |
| zurich_building_lod2 | id-miss | CityJSONSeq | cityjsonseq | median | 1.153105 | 1.141039 | 1.159745 | 1.130436 | 1.348153 | 545931264 | 7.943786 | 30638080 | 6.8890 | 0.0561 | |
| zurich_building_lod2 | id-miss | FlatCityBuf | flatcitybuf | median | 0.125659 | 0.118436 | 0.128178 | 0.114669 | 0.186499 | 7815168 | 7.943786 | 30638080 | 63.2170 | 3.9203 | |
| zurich_building_lod2 | id-miss | CityParquet | cityparquet | median | 0.000417 | 0.000393 | 0.000446 | 0.000379 | 0.000636 | 10551296 | 7.943786 | 30638080 | 19049.8465 | 2.9037 | |
| tokyo | full-read | CityGML | citygml | median | 6.894654 | 6.861306 | 6.968042 | 6.804715 | 7.702013 | 87146496 | 6.894654 | 87146496 | 1.0000 | 1.0000 | |
| tokyo | full-read | CityJSON | cityjson | median | 1.632682 | 1.588904 | 1.659916 | 1.563941 | 2.672065 | 4351754240 | 6.894654 | 87146496 | 4.2229 | 0.0200 | |
| tokyo | full-read | CityJSONSeq | cityjsonseq | median | 1.270648 | 1.262620 | 1.275744 | 1.254024 | 1.288774 | 874086400 | 6.894654 | 87146496 | 5.4261 | 0.0997 | |
| tokyo | full-read | FlatCityBuf | flatcitybuf | median | 0.097112 | 0.096566 | 0.097614 | 0.095639 | 0.101317 | 8978432 | 6.894654 | 87146496 | 70.9969 | 9.7062 | |
| tokyo | full-read | CityParquet | cityparquet | median | 0.499168 | 0.497097 | 0.502607 | 0.493900 | 0.537703 | 247775232 | 6.894654 | 87146496 | 13.8123 | 0.3517 | |
| tokyo | count | CityGML | citygml | median | 6.792130 | 6.768861 | 6.848758 | 6.752483 | 6.960765 | 86687744 | 6.792130 | 86687744 | 1.0000 | 1.0000 | |
| tokyo | count | CityJSON | cityjson | median | 1.510686 | 1.438357 | 1.530010 | 1.358941 | 1.635041 | 4353802240 | 6.792130 | 86687744 | 4.4961 | 0.0199 | |
| tokyo | count | CityJSONSeq | cityjsonseq | median | 1.219776 | 1.213247 | 1.230185 | 1.206562 | 1.249354 | 874921984 | 6.792130 | 86687744 | 5.5683 | 0.0991 | |
| tokyo | count | FlatCityBuf | flatcitybuf | median | 0.000033 | 0.000029 | 0.000039 | 0.000026 | 0.000051 | 7307264 | 6.792130 | 86687744 | 205822.1212 | 11.8632 | |
| tokyo | count | CityParquet | cityparquet | median | 0.000290 | 0.000282 | 0.000296 | 0.000275 | 0.000326 | 9469952 | 6.792130 | 86687744 | 23421.1379 | 9.1540 | |
| tokyo | bbox-1pct | CityGML | citygml | median | 6.914795 | 6.879975 | 6.972159 | 6.807312 | 7.257033 | 86556672 | 6.914795 | 86556672 | 1.0000 | 1.0000 | |
| tokyo | bbox-1pct | CityJSON | cityjson | median | 1.650051 | 1.634202 | 1.687901 | 1.544751 | 2.133400 | 4358356992 | 6.914795 | 86556672 | 4.1907 | 0.0199 | |
| tokyo | bbox-1pct | CityJSONSeq | cityjsonseq | median | 1.269623 | 1.263896 | 1.276654 | 1.257859 | 1.298484 | 874954752 | 6.914795 | 86556672 | 5.4463 | 0.0989 | |
| tokyo | bbox-1pct | FlatCityBuf | flatcitybuf | median | 0.001300 | 0.001272 | 0.001332 | 0.001223 | 0.001869 | 8142848 | 6.914795 | 86556672 | 5319.0731 | 10.6298 | |
| tokyo | bbox-1pct | CityParquet | cityparquet | median | 0.027307 | 0.027162 | 0.027619 | 0.026865 | 0.031272 | 56262656 | 6.914795 | 86556672 | 253.2243 | 1.5384 | |
| tokyo | bbox-5pct | CityGML | citygml | median | 6.833645 | 6.579792 | 6.927875 | 6.417738 | 10.891882 | 86851584 | 6.833645 | 86851584 | 1.0000 | 1.0000 | |
| tokyo | bbox-5pct | CityJSON | cityjson | median | 1.684731 | 1.630343 | 1.788458 | 1.596692 | 3.137970 | 4358373376 | 6.833645 | 86851584 | 4.0562 | 0.0199 | |
| tokyo | bbox-5pct | CityJSONSeq | cityjsonseq | median | 1.279655 | 1.272983 | 1.295017 | 1.260728 | 1.382729 | 874889216 | 6.833645 | 86851584 | 5.3402 | 0.0993 | |
| tokyo | bbox-5pct | FlatCityBuf | flatcitybuf | median | 0.006094 | 0.006033 | 0.006317 | 0.005967 | 0.010162 | 8355840 | 6.833645 | 86851584 | 1121.3727 | 10.3941 | |
| tokyo | bbox-5pct | CityParquet | cityparquet | median | 0.039448 | 0.039065 | 0.039729 | 0.038157 | 0.041303 | 78086144 | 6.833645 | 86851584 | 173.2317 | 1.1123 | |
| tokyo | bbox-25pct | CityGML | citygml | median | 7.123625 | 6.903588 | 7.216454 | 6.767172 | 7.641177 | 86261760 | 7.123625 | 86261760 | 1.0000 | 1.0000 | |
| tokyo | bbox-25pct | CityJSON | cityjson | median | 1.921800 | 1.739612 | 2.355130 | 1.610275 | 2.654974 | 4357898240 | 7.123625 | 86261760 | 3.7067 | 0.0198 | |
| tokyo | bbox-25pct | CityJSONSeq | cityjsonseq | median | 1.288423 | 1.284773 | 1.304055 | 1.267978 | 1.352899 | 875036672 | 7.123625 | 86261760 | 5.5289 | 0.0986 | |
| tokyo | bbox-25pct | FlatCityBuf | flatcitybuf | median | 0.033118 | 0.032509 | 0.033581 | 0.031816 | 0.034420 | 10797056 | 7.123625 | 86261760 | 215.0983 | 7.9894 | |
| tokyo | bbox-25pct | CityParquet | cityparquet | median | 0.096330 | 0.095472 | 0.098512 | 0.094758 | 0.100624 | 154828800 | 7.123625 | 86261760 | 73.9502 | 0.5571 | |
| tokyo | attr-filter | CityGML | citygml | median | 6.949277 | 6.878664 | 7.101914 | 6.857628 | 7.402983 | 86540288 | 6.949277 | 86540288 | 1.0000 | 1.0000 | |
| tokyo | attr-filter | CityJSON | cityjson | median | 1.476854 | 1.450566 | 1.518630 | 1.433331 | 1.696794 | 4352360448 | 6.949277 | 86540288 | 4.7055 | 0.0199 | |
| tokyo | attr-filter | CityJSONSeq | cityjsonseq | median | 1.239883 | 1.229935 | 1.244509 | 1.212538 | 1.302305 | 875003904 | 6.949277 | 86540288 | 5.6048 | 0.0989 | |
| tokyo | attr-filter | FlatCityBuf | flatcitybuf | median | 0.044636 | 0.044410 | 0.045088 | 0.044029 | 0.048688 | 11206656 | 6.949277 | 86540288 | 155.6877 | 7.7222 | |
| tokyo | attr-filter | CityParquet | cityparquet | median | 0.003546 | 0.003510 | 0.003574 | 0.003429 | 0.004439 | 16154624 | 6.949277 | 86540288 | 1959.7510 | 5.3570 | |
| tokyo | attr-stats | CityGML | citygml | median | 6.938712 | 6.895778 | 7.052220 | 6.864418 | 8.022943 | 86589440 | 6.938712 | 86589440 | 1.0000 | 1.0000 | |
| tokyo | attr-stats | CityJSON | cityjson | median | 1.437151 | 1.430615 | 1.466551 | 1.377898 | 1.646465 | 4352180224 | 6.938712 | 86589440 | 4.8281 | 0.0199 | |
| tokyo | attr-stats | CityJSONSeq | cityjsonseq | median | 1.231822 | 1.218729 | 1.253903 | 1.209202 | 2.191327 | 874708992 | 6.938712 | 86589440 | 5.6329 | 0.0990 | |
| tokyo | attr-stats | FlatCityBuf | flatcitybuf | median | 0.078687 | 0.078287 | 0.078964 | 0.077139 | 0.080185 | 8994816 | 6.938712 | 86589440 | 88.1812 | 9.6266 | |
| tokyo | attr-stats | CityParquet | cityparquet | median | 0.000576 | 0.000552 | 0.000603 | 0.000537 | 0.000717 | 10633216 | 6.938712 | 86589440 | 12046.3750 | 8.1433 | |
| tokyo | id-10pct | CityGML | citygml | median | 0.716659 | 0.711822 | 0.719727 | 0.706835 | 0.831570 | 85524480 | 0.716659 | 85524480 | 1.0000 | 1.0000 | |
| tokyo | id-10pct | CityJSON | cityjson | median | 1.450745 | 1.417104 | 1.569871 | 1.372132 | 2.029949 | 4351737856 | 0.716659 | 85524480 | 0.4940 | 0.0197 | |
| tokyo | id-10pct | CityJSONSeq | cityjsonseq | median | 0.248731 | 0.244259 | 0.252889 | 0.239432 | 0.273870 | 865271808 | 0.716659 | 85524480 | 2.8813 | 0.0988 | |
| tokyo | id-10pct | FlatCityBuf | flatcitybuf | median | 0.073711 | 0.073390 | 0.073962 | 0.072122 | 0.079173 | 9043968 | 0.716659 | 85524480 | 9.7226 | 9.4565 | |
| tokyo | id-10pct | CityParquet | cityparquet | median | 0.025023 | 0.024513 | 0.025348 | 0.024090 | 0.025663 | 55869440 | 0.716659 | 85524480 | 28.6400 | 1.5308 | |
| tokyo | id-50pct | CityGML | citygml | median | 4.268869 | 4.162389 | 4.416784 | 4.093307 | 4.683911 | 86523904 | 4.268869 | 86523904 | 1.0000 | 1.0000 | |
| tokyo | id-50pct | CityJSON | cityjson | median | 1.876708 | 1.560709 | 2.052312 | 1.446277 | 2.541734 | 4351901696 | 4.268869 | 86523904 | 2.2747 | 0.0199 | |
| tokyo | id-50pct | CityJSONSeq | cityjsonseq | median | 0.812717 | 0.792134 | 0.835968 | 0.770294 | 0.996886 | 874889216 | 4.268869 | 86523904 | 5.2526 | 0.0989 | |
| tokyo | id-50pct | FlatCityBuf | flatcitybuf | median | 0.035365 | 0.035119 | 0.035652 | 0.034506 | 0.038081 | 8241152 | 4.268869 | 86523904 | 120.7089 | 10.4990 | |
| tokyo | id-50pct | CityParquet | cityparquet | median | 0.058365 | 0.057594 | 0.058974 | 0.056776 | 0.062316 | 62750720 | 4.268869 | 86523904 | 73.1409 | 1.3789 | |
| tokyo | id-90pct | CityGML | citygml | median | 6.551991 | 6.438279 | 6.599438 | 6.399542 | 6.890262 | 86491136 | 6.551991 | 86491136 | 1.0000 | 1.0000 | |
| tokyo | id-90pct | CityJSON | cityjson | median | 1.545311 | 1.487387 | 1.630389 | 1.429048 | 1.992485 | 4351754240 | 6.551991 | 86491136 | 4.2399 | 0.0199 | |
| tokyo | id-90pct | CityJSONSeq | cityjsonseq | median | 1.187503 | 1.177163 | 1.233761 | 1.163469 | 1.357610 | 875003904 | 6.551991 | 86491136 | 5.5175 | 0.0988 | |
| tokyo | id-90pct | FlatCityBuf | flatcitybuf | median | 0.020478 | 0.020346 | 0.020630 | 0.020096 | 0.021509 | 7798784 | 6.551991 | 86491136 | 319.9527 | 11.0903 | |
| tokyo | id-90pct | CityParquet | cityparquet | median | 0.048731 | 0.048453 | 0.049661 | 0.047977 | 0.052956 | 58523648 | 6.551991 | 86491136 | 134.4522 | 1.4779 | |
| tokyo | id-miss | CityGML | citygml | median | 6.973289 | 6.850294 | 7.063171 | 6.780190 | 7.376693 | 86736896 | 6.973289 | 86736896 | 1.0000 | 1.0000 | |
| tokyo | id-miss | CityJSON | cityjson | median | 1.529740 | 1.482203 | 1.679399 | 1.418707 | 2.043916 | 4350263296 | 6.973289 | 86736896 | 4.5585 | 0.0199 | |
| tokyo | id-miss | CityJSONSeq | cityjsonseq | median | 1.236832 | 1.232548 | 1.251173 | 1.221425 | 1.298168 | 875003904 | 6.973289 | 86736896 | 5.6380 | 0.0991 | |
| tokyo | id-miss | FlatCityBuf | flatcitybuf | median | 0.075834 | 0.075510 | 0.076381 | 0.074691 | 0.078722 | 9060352 | 6.973289 | 86736896 | 91.9547 | 9.5732 | |
| tokyo | id-miss | CityParquet | cityparquet | median | 0.000490 | 0.000461 | 0.000517 | 0.000427 | 0.000600 | 10715136 | 6.973289 | 86736896 | 14231.2020 | 8.0948 | |
| montreal | full-read | CityGML | citygml | median | 16.061205 | 15.985616 | 16.332696 | 15.776352 | 17.579648 | 171507712 | 16.061205 | 171507712 | 1.0000 | 1.0000 | |
| montreal | full-read | CityJSON | cityjson | median | 3.332108 | 3.138148 | 3.489465 | 2.968455 | 4.472478 | 8003059712 | 16.061205 | 171507712 | 4.8201 | 0.0214 | |
| montreal | full-read | CityJSONSeq | cityjsonseq | median | 1.766444 | 1.759578 | 1.778961 | 1.747946 | 1.818077 | 945881088 | 16.061205 | 171507712 | 9.0924 | 0.1813 | |
| montreal | full-read | FlatCityBuf | flatcitybuf | median | 0.075662 | 0.075343 | 0.076208 | 0.073846 | 0.077899 | 12419072 | 16.061205 | 171507712 | 212.2757 | 13.8100 | |
| montreal | full-read | CityParquet | cityparquet | median | 0.571887 | 0.568548 | 0.574627 | 0.562225 | 0.581322 | 462028800 | 16.061205 | 171507712 | 28.0846 | 0.3712 | |
| montreal | count | CityGML | citygml | median | 15.679072 | 15.553589 | 16.021979 | 15.370716 | 16.272110 | 172883968 | 15.679072 | 172883968 | 1.0000 | 1.0000 | |
| montreal | count | CityJSON | cityjson | median | 2.790579 | 2.723892 | 2.879128 | 2.629613 | 3.355390 | 7591690240 | 15.679072 | 172883968 | 5.6186 | 0.0228 | |
| montreal | count | CityJSONSeq | cityjsonseq | median | 1.680535 | 1.672841 | 1.690381 | 1.662240 | 1.702876 | 945455104 | 15.679072 | 172883968 | 9.3298 | 0.1829 | |
| montreal | count | FlatCityBuf | flatcitybuf | median | 0.000025 | 0.000024 | 0.000027 | 0.000022 | 0.000030 | 7290880 | 15.679072 | 172883968 | 627162.8800 | 23.7124 | |
| montreal | count | CityParquet | cityparquet | median | 0.000224 | 0.000217 | 0.000236 | 0.000211 | 0.000244 | 9273344 | 15.679072 | 172883968 | 69995.8571 | 18.6431 | |
| montreal | bbox-1pct | CityGML | citygml | median | 15.473683 | 15.439123 | 15.583929 | 15.362840 | 16.173161 | 173588480 | 15.473683 | 173588480 | 1.0000 | 1.0000 | |
| montreal | bbox-1pct | CityJSON | cityjson | median | 3.397542 | 3.278682 | 3.632258 | 2.884057 | 5.015980 | 7639728128 | 15.473683 | 173588480 | 4.5544 | 0.0227 | |
| montreal | bbox-1pct | CityJSONSeq | cityjsonseq | median | 1.760533 | 1.751990 | 1.768880 | 1.740941 | 1.791968 | 945995776 | 15.473683 | 173588480 | 8.7892 | 0.1835 | |
| montreal | bbox-1pct | FlatCityBuf | flatcitybuf | median | 0.001504 | 0.001487 | 0.001616 | 0.001451 | 0.001824 | 9420800 | 15.473683 | 173588480 | 10288.3531 | 18.4261 | |
| montreal | bbox-1pct | CityParquet | cityparquet | median | 0.048217 | 0.047631 | 0.049004 | 0.046508 | 0.057426 | 169066496 | 15.473683 | 173588480 | 320.9176 | 1.0267 | |
| montreal | bbox-5pct | CityGML | citygml | median | 15.798310 | 15.560033 | 15.908418 | 15.466186 | 16.786453 | 172376064 | 15.798310 | 172376064 | 1.0000 | 1.0000 | |
| montreal | bbox-5pct | CityJSON | cityjson | median | 3.661621 | 3.476701 | 3.731183 | 3.248615 | 4.093752 | 7469465600 | 15.798310 | 172376064 | 4.3146 | 0.0231 | |
| montreal | bbox-5pct | CityJSONSeq | cityjsonseq | median | 1.772445 | 1.761616 | 1.783755 | 1.748300 | 1.866688 | 946012160 | 15.798310 | 172376064 | 8.9133 | 0.1822 | |
| montreal | bbox-5pct | FlatCityBuf | flatcitybuf | median | 0.005499 | 0.005451 | 0.005596 | 0.005339 | 0.005991 | 9633792 | 15.798310 | 172376064 | 2872.9424 | 17.8929 | |
| montreal | bbox-5pct | CityParquet | cityparquet | median | 0.065201 | 0.064555 | 0.066459 | 0.063720 | 0.073901 | 185925632 | 15.798310 | 172376064 | 242.3017 | 0.9271 | |
| montreal | bbox-25pct | CityGML | citygml | median | 15.603346 | 15.585559 | 15.833335 | 15.419012 | 16.681437 | 172359680 | 15.603346 | 172359680 | 1.0000 | 1.0000 | |
| montreal | bbox-25pct | CityJSON | cityjson | median | 3.972893 | 3.877275 | 4.119732 | 3.719664 | 4.367774 | 6486360064 | 15.603346 | 172359680 | 3.9275 | 0.0266 | |
| montreal | bbox-25pct | CityJSONSeq | cityjsonseq | median | 1.782823 | 1.774434 | 1.790080 | 1.766528 | 1.873997 | 945963008 | 15.603346 | 172359680 | 8.7520 | 0.1822 | |
| montreal | bbox-25pct | FlatCityBuf | flatcitybuf | median | 0.024447 | 0.024357 | 0.024781 | 0.024085 | 0.027040 | 11780096 | 15.603346 | 172359680 | 638.2520 | 14.6314 | |
| montreal | bbox-25pct | CityParquet | cityparquet | median | 0.143967 | 0.143350 | 0.144536 | 0.142558 | 0.147147 | 257867776 | 15.603346 | 172359680 | 108.3814 | 0.6684 | |
| montreal | attr-filter | CityGML | citygml | median | 15.563448 | 15.492409 | 15.714175 | 15.309056 | 16.413276 | 173473792 | 15.563448 | 173473792 | 1.0000 | 1.0000 | |
| montreal | attr-filter | CityJSON | cityjson | median | 3.374618 | 3.198758 | 3.562791 | 2.931434 | 4.200909 | 6737510400 | 15.563448 | 173473792 | 4.6119 | 0.0257 | |
| montreal | attr-filter | CityJSONSeq | cityjsonseq | median | 1.687857 | 1.681780 | 1.703557 | 1.669426 | 1.756311 | 945881088 | 15.563448 | 173473792 | 9.2208 | 0.1834 | |
| montreal | attr-filter | FlatCityBuf | flatcitybuf | median | 0.026497 | 0.026309 | 0.027169 | 0.025740 | 0.029239 | 13959168 | 15.563448 | 173473792 | 587.3664 | 12.4272 | |
| montreal | attr-filter | CityParquet | cityparquet | median | 0.001502 | 0.001446 | 0.001566 | 0.001408 | 0.002038 | 13254656 | 15.563448 | 173473792 | 10361.8162 | 13.0878 | |
| montreal | attr-stats | CityGML | citygml | median | 15.503222 | 15.408936 | 15.567770 | 15.319501 | 16.116206 | 171606016 | 15.503222 | 171606016 | 1.0000 | 1.0000 | |
| montreal | attr-stats | CityJSON | cityjson | median | 3.534648 | 3.304663 | 3.701200 | 3.045804 | 3.850011 | 6049628160 | 15.503222 | 171606016 | 4.3861 | 0.0284 | |
| montreal | attr-stats | CityJSONSeq | cityjsonseq | median | 1.674845 | 1.667914 | 1.688331 | 1.656278 | 1.798661 | 945586176 | 15.503222 | 171606016 | 9.2565 | 0.1815 | |
| montreal | attr-stats | FlatCityBuf | flatcitybuf | median | 0.055799 | 0.055530 | 0.056483 | 0.054954 | 0.058649 | 12435456 | 15.503222 | 171606016 | 277.8405 | 13.7997 | |
| montreal | attr-stats | CityParquet | cityparquet | median | 0.000505 | 0.000473 | 0.000554 | 0.000425 | 0.001057 | 10797056 | 15.503222 | 171606016 | 30699.4495 | 15.8938 | |
| montreal | id-10pct | CityGML | citygml | median | 1.323900 | 1.308674 | 1.341589 | 1.292768 | 2.070140 | 77611008 | 1.323900 | 77611008 | 1.0000 | 1.0000 | |
| montreal | id-10pct | CityJSON | cityjson | median | 3.181948 | 3.091700 | 3.270210 | 2.946844 | 3.769883 | 6350782464 | 1.323900 | 77611008 | 0.4161 | 0.0122 | |
| montreal | id-10pct | CityJSONSeq | cityjsonseq | median | 0.285597 | 0.283133 | 0.293660 | 0.281034 | 0.306050 | 927662080 | 1.323900 | 77611008 | 4.6356 | 0.0837 | |
| montreal | id-10pct | FlatCityBuf | flatcitybuf | median | 0.052511 | 0.052183 | 0.053263 | 0.051768 | 0.054970 | 12468224 | 1.323900 | 77611008 | 25.2119 | 6.2247 | |
| montreal | id-10pct | CityParquet | cityparquet | median | 0.018545 | 0.018317 | 0.018823 | 0.018177 | 0.019185 | 60866560 | 1.323900 | 77611008 | 71.3885 | 1.2751 | |
| montreal | id-50pct | CityGML | citygml | median | 6.816978 | 6.765346 | 6.902692 | 6.724949 | 7.134450 | 110346240 | 6.816978 | 110346240 | 1.0000 | 1.0000 | |
| montreal | id-50pct | CityJSON | cityjson | median | 3.169253 | 3.088199 | 3.252889 | 2.936739 | 3.717913 | 6395379712 | 6.816978 | 110346240 | 2.1510 | 0.0173 | |
| montreal | id-50pct | CityJSONSeq | cityjsonseq | median | 0.843711 | 0.833477 | 0.854262 | 0.826997 | 0.865903 | 946126848 | 6.816978 | 110346240 | 8.0798 | 0.1166 | |
| montreal | id-50pct | FlatCityBuf | flatcitybuf | median | 0.030895 | 0.030573 | 0.031414 | 0.030182 | 0.031989 | 12468224 | 6.816978 | 110346240 | 220.6499 | 8.8502 | |
| montreal | id-50pct | CityParquet | cityparquet | median | 0.073984 | 0.073683 | 0.074571 | 0.072033 | 0.075686 | 69074944 | 6.816978 | 110346240 | 92.1412 | 1.5975 | |
| montreal | id-90pct | CityGML | citygml | median | 13.302819 | 13.238063 | 13.434745 | 13.203411 | 13.983446 | 170950656 | 13.302819 | 170950656 | 1.0000 | 1.0000 | |
| montreal | id-90pct | CityJSON | cityjson | median | 3.160878 | 3.069128 | 3.440739 | 2.951097 | 3.624274 | 6273449984 | 13.302819 | 170950656 | 4.2086 | 0.0272 | |
| montreal | id-90pct | CityJSONSeq | cityjsonseq | median | 1.474347 | 1.466646 | 1.482507 | 1.462310 | 1.502929 | 945717248 | 13.302819 | 170950656 | 9.0229 | 0.1808 | |
| montreal | id-90pct | FlatCityBuf | flatcitybuf | median | 0.005131 | 0.005000 | 0.005200 | 0.004844 | 0.005398 | 9682944 | 13.302819 | 170950656 | 2592.6367 | 17.6548 | |
| montreal | id-90pct | CityParquet | cityparquet | median | 0.152132 | 0.150266 | 0.152727 | 0.148779 | 0.154872 | 90013696 | 13.302819 | 170950656 | 87.4426 | 1.8992 | |
| montreal | id-miss | CityGML | citygml | median | 15.810880 | 15.553959 | 16.056795 | 15.109752 | 19.617275 | 171180032 | 15.810880 | 171180032 | 1.0000 | 1.0000 | |
| montreal | id-miss | CityJSON | cityjson | median | 3.223490 | 3.092908 | 3.279130 | 2.929757 | 3.538917 | 6384730112 | 15.810880 | 171180032 | 4.9049 | 0.0268 | |
| montreal | id-miss | CityJSONSeq | cityjsonseq | median | 1.683631 | 1.675533 | 1.689768 | 1.660260 | 1.801305 | 946520064 | 15.810880 | 171180032 | 9.3909 | 0.1809 | |
| montreal | id-miss | FlatCityBuf | flatcitybuf | median | 0.055799 | 0.054526 | 0.056145 | 0.053784 | 0.057027 | 12468224 | 15.810880 | 171180032 | 283.3542 | 13.7293 | |
| montreal | id-miss | CityParquet | cityparquet | median | 0.000330 | 0.000324 | 0.000341 | 0.000316 | 0.000367 | 10452992 | 15.810880 | 171180032 | 47911.7576 | 16.3762 | |
| nyc_da13_buildings | full-read | CityGML | citygml | median | 2.646219 | 2.638185 | 2.654357 | 2.623433 | 2.765849 | 20119552 | 2.646219 | 20119552 | 1.0000 | 1.0000 | |
| nyc_da13_buildings | full-read | CityJSON | cityjson | median | 0.720496 | 0.709934 | 0.731518 | 0.698392 | 0.770861 | 1585283072 | 2.646219 | 20119552 | 3.6728 | 0.0127 | |
| nyc_da13_buildings | full-read | CityJSONSeq | cityjsonseq | median | 0.517709 | 0.515391 | 0.521410 | 0.512169 | 0.530212 | 250511360 | 2.646219 | 20119552 | 5.1114 | 0.0803 | |
| nyc_da13_buildings | full-read | FlatCityBuf | flatcitybuf | median | 0.044504 | 0.043816 | 0.045879 | 0.043182 | 0.074521 | 7766016 | 2.646219 | 20119552 | 59.4603 | 2.5907 | |
| nyc_da13_buildings | full-read | CityParquet | cityparquet | median | 0.336075 | 0.334574 | 0.338785 | 0.331042 | 0.351435 | 97976320 | 2.646219 | 20119552 | 7.8739 | 0.2054 | |
| nyc_da13_buildings | count | CityGML | citygml | median | 2.636279 | 2.616459 | 2.671990 | 2.603854 | 2.903572 | 20152320 | 2.636279 | 20152320 | 1.0000 | 1.0000 | |
| nyc_da13_buildings | count | CityJSON | cityjson | median | 0.628778 | 0.622106 | 0.634543 | 0.612639 | 0.656696 | 1585168384 | 2.636279 | 20152320 | 4.1927 | 0.0127 | |
| nyc_da13_buildings | count | CityJSONSeq | cityjsonseq | median | 0.502421 | 0.496017 | 0.512844 | 0.491751 | 0.552665 | 250511360 | 2.636279 | 20152320 | 5.2472 | 0.0804 | |
| nyc_da13_buildings | count | FlatCityBuf | flatcitybuf | median | 0.000025 | 0.000025 | 0.000027 | 0.000023 | 0.000054 | 7290880 | 2.636279 | 20152320 | 105451.1600 | 2.7640 | |
| nyc_da13_buildings | count | CityParquet | cityparquet | median | 0.000236 | 0.000219 | 0.000261 | 0.000195 | 0.000294 | 9142272 | 2.636279 | 20152320 | 11170.6737 | 2.2043 | |
| nyc_da13_buildings | bbox-1pct | CityGML | citygml | median | 2.635790 | 2.621837 | 2.646101 | 2.596603 | 2.688300 | 20152320 | 2.635790 | 20152320 | 1.0000 | 1.0000 | |
| nyc_da13_buildings | bbox-1pct | CityJSON | cityjson | median | 0.710431 | 0.696869 | 0.726317 | 0.673705 | 0.768658 | 1589313536 | 2.635790 | 20152320 | 3.7101 | 0.0127 | |
| nyc_da13_buildings | bbox-1pct | CityJSONSeq | cityjsonseq | median | 0.516328 | 0.513143 | 0.517943 | 0.511071 | 0.561059 | 247513088 | 2.635790 | 20152320 | 5.1049 | 0.0814 | |
| nyc_da13_buildings | bbox-1pct | FlatCityBuf | flatcitybuf | median | 0.000500 | 0.000493 | 0.000506 | 0.000455 | 0.000597 | 7782400 | 2.635790 | 20152320 | 5271.5800 | 2.5895 | |
| nyc_da13_buildings | bbox-1pct | CityParquet | cityparquet | median | 0.011080 | 0.010730 | 0.012177 | 0.009783 | 0.036293 | 29900800 | 2.635790 | 20152320 | 237.8872 | 0.6740 | |
| nyc_da13_buildings | bbox-5pct | CityGML | citygml | median | 2.632933 | 2.626494 | 2.644098 | 2.608792 | 2.759328 | 20135936 | 2.632933 | 20135936 | 1.0000 | 1.0000 | |
| nyc_da13_buildings | bbox-5pct | CityJSON | cityjson | median | 0.708344 | 0.691986 | 0.718581 | 0.683902 | 0.759764 | 1589706752 | 2.632933 | 20135936 | 3.7170 | 0.0127 | |
| nyc_da13_buildings | bbox-5pct | CityJSONSeq | cityjsonseq | median | 0.517256 | 0.515878 | 0.528103 | 0.509775 | 0.600960 | 250658816 | 2.632933 | 20135936 | 5.0902 | 0.0803 | |
| nyc_da13_buildings | bbox-5pct | FlatCityBuf | flatcitybuf | median | 0.002138 | 0.002099 | 0.002181 | 0.002063 | 0.004110 | 8044544 | 2.632933 | 20135936 | 1231.4935 | 2.5031 | |
| nyc_da13_buildings | bbox-5pct | CityParquet | cityparquet | median | 0.020794 | 0.020565 | 0.021014 | 0.020258 | 0.021475 | 42106880 | 2.632933 | 20135936 | 126.6198 | 0.4782 | |
| nyc_da13_buildings | bbox-25pct | CityGML | citygml | median | 2.638418 | 2.622335 | 2.655671 | 2.607754 | 2.718740 | 20201472 | 2.638418 | 20201472 | 1.0000 | 1.0000 | |
| nyc_da13_buildings | bbox-25pct | CityJSON | cityjson | median | 0.721281 | 0.708897 | 0.745718 | 0.693692 | 0.873829 | 1589608448 | 2.638418 | 20201472 | 3.6580 | 0.0127 | |
| nyc_da13_buildings | bbox-25pct | CityJSONSeq | cityjsonseq | median | 0.517625 | 0.515822 | 0.522425 | 0.511386 | 0.573320 | 250593280 | 2.638418 | 20201472 | 5.0972 | 0.0806 | |
| nyc_da13_buildings | bbox-25pct | FlatCityBuf | flatcitybuf | median | 0.010198 | 0.010041 | 0.010480 | 0.009898 | 0.012374 | 8617984 | 2.638418 | 20201472 | 258.7192 | 2.3441 | |
| nyc_da13_buildings | bbox-25pct | CityParquet | cityparquet | median | 0.033280 | 0.033140 | 0.033803 | 0.032476 | 0.037395 | 52248576 | 2.638418 | 20201472 | 79.2794 | 0.3866 | |
| nyc_da13_buildings | attr-filter | CityGML | citygml | median | 2.624127 | 2.616317 | 2.640847 | 2.597098 | 2.703479 | 20086784 | 2.624127 | 20086784 | 1.0000 | 1.0000 | |
| nyc_da13_buildings | attr-filter | CityJSON | cityjson | median | 0.642727 | 0.636717 | 0.660542 | 0.627295 | 0.728736 | 1585283072 | 2.624127 | 20086784 | 4.0828 | 0.0127 | |
| nyc_da13_buildings | attr-filter | CityJSONSeq | cityjsonseq | median | 0.533563 | 0.517486 | 0.615401 | 0.500140 | 0.781178 | 250904576 | 2.624127 | 20086784 | 4.9181 | 0.0801 | |
| nyc_da13_buildings | attr-filter | FlatCityBuf | flatcitybuf | median | 0.000389 | 0.000364 | 0.000416 | 0.000341 | 0.000504 | 7716864 | 2.624127 | 20086784 | 6745.8278 | 2.6030 | |
| nyc_da13_buildings | attr-filter | CityParquet | cityparquet | median | 0.001451 | 0.001436 | 0.001468 | 0.001423 | 0.001779 | 12861440 | 2.624127 | 20086784 | 1808.4955 | 1.5618 | |
| nyc_da13_buildings | id-10pct | CityGML | citygml | median | 0.280160 | 0.277698 | 0.280918 | 0.275579 | 0.284047 | 17203200 | 0.280160 | 17203200 | 1.0000 | 1.0000 | |
| nyc_da13_buildings | id-10pct | CityJSON | cityjson | median | 0.640247 | 0.626932 | 0.648119 | 0.599402 | 0.701315 | 1585266688 | 0.280160 | 17203200 | 0.4376 | 0.0109 | |
| nyc_da13_buildings | id-10pct | CityJSONSeq | cityjsonseq | median | 0.089417 | 0.087934 | 0.091835 | 0.085282 | 0.096477 | 249675776 | 0.280160 | 17203200 | 3.1332 | 0.0689 | |
| nyc_da13_buildings | id-10pct | FlatCityBuf | flatcitybuf | median | 0.007200 | 0.006913 | 0.007339 | 0.006747 | 0.007952 | 7602176 | 0.280160 | 17203200 | 38.9111 | 2.2629 | |
| nyc_da13_buildings | id-10pct | CityParquet | cityparquet | median | 0.008271 | 0.008196 | 0.008406 | 0.008018 | 0.008691 | 33964032 | 0.280160 | 17203200 | 33.8726 | 0.5065 | |
| nyc_da13_buildings | id-50pct | CityGML | citygml | median | 1.327533 | 1.314944 | 1.333462 | 1.306660 | 1.555449 | 17956864 | 1.327533 | 17956864 | 1.0000 | 1.0000 | |
| nyc_da13_buildings | id-50pct | CityJSON | cityjson | median | 0.649663 | 0.641694 | 0.665054 | 0.627409 | 0.732369 | 1585332224 | 1.327533 | 17956864 | 2.0434 | 0.0113 | |
| nyc_da13_buildings | id-50pct | CityJSONSeq | cityjsonseq | median | 0.273807 | 0.271621 | 0.276313 | 0.268527 | 0.323226 | 249856000 | 1.327533 | 17956864 | 4.8484 | 0.0719 | |
| nyc_da13_buildings | id-50pct | FlatCityBuf | flatcitybuf | median | 0.006798 | 0.006622 | 0.006954 | 0.006400 | 0.007349 | 7602176 | 1.327533 | 17956864 | 195.2829 | 2.3621 | |
| nyc_da13_buildings | id-50pct | CityParquet | cityparquet | median | 0.008389 | 0.008295 | 0.008567 | 0.008174 | 0.009554 | 33947648 | 1.327533 | 17956864 | 158.2469 | 0.5290 | |
| nyc_da13_buildings | id-90pct | CityGML | citygml | median | 2.377661 | 2.367843 | 2.393292 | 2.344759 | 2.784945 | 18481152 | 2.377661 | 18481152 | 1.0000 | 1.0000 | |
| nyc_da13_buildings | id-90pct | CityJSON | cityjson | median | 0.637145 | 0.618914 | 0.647465 | 0.603427 | 0.666599 | 1585250304 | 2.377661 | 18481152 | 3.7317 | 0.0117 | |
| nyc_da13_buildings | id-90pct | CityJSONSeq | cityjsonseq | median | 0.454246 | 0.451266 | 0.459726 | 0.447137 | 0.481422 | 249757696 | 2.377661 | 18481152 | 5.2343 | 0.0740 | |
| nyc_da13_buildings | id-90pct | FlatCityBuf | flatcitybuf | median | 0.024261 | 0.023706 | 0.024933 | 0.023462 | 0.025294 | 7815168 | 2.377661 | 18481152 | 98.0034 | 2.3648 | |
| nyc_da13_buildings | id-90pct | CityParquet | cityparquet | median | 0.008273 | 0.008201 | 0.008341 | 0.008079 | 0.008829 | 34471936 | 2.377661 | 18481152 | 287.4001 | 0.5361 | |
| nyc_da13_buildings | id-miss | CityGML | citygml | median | 2.661335 | 2.645280 | 2.669284 | 2.622849 | 2.886792 | 20086784 | 2.661335 | 20086784 | 1.0000 | 1.0000 | |
| nyc_da13_buildings | id-miss | CityJSON | cityjson | median | 0.629223 | 0.621079 | 0.634112 | 0.604188 | 0.670124 | 1585217536 | 2.661335 | 20086784 | 4.2296 | 0.0127 | |
| nyc_da13_buildings | id-miss | CityJSONSeq | cityjsonseq | median | 0.498003 | 0.494988 | 0.502084 | 0.493201 | 0.532970 | 250494976 | 2.661335 | 20086784 | 5.3440 | 0.0802 | |
| nyc_da13_buildings | id-miss | FlatCityBuf | flatcitybuf | median | 0.033833 | 0.032539 | 0.034348 | 0.031907 | 0.036414 | 7815168 | 2.661335 | 20086784 | 78.6609 | 2.5702 | |
| nyc_da13_buildings | id-miss | CityParquet | cityparquet | median | 0.001221 | 0.001181 | 0.001254 | 0.001134 | 0.001503 | 13320192 | 2.661335 | 20086784 | 2179.6355 | 1.5080 | |
| vienna_102081 | full-read | CityGML | citygml | median | 0.120587 | 0.119030 | 0.122051 | 0.117465 | 0.130667 | 13516800 | 0.120587 | 13516800 | 1.0000 | 1.0000 | |
| vienna_102081 | full-read | CityJSON | cityjson | median | 0.023128 | 0.022915 | 0.023780 | 0.022576 | 0.024188 | 63340544 | 0.120587 | 13516800 | 5.2139 | 0.2134 | |
| vienna_102081 | full-read | CityJSONSeq | cityjsonseq | median | 0.019939 | 0.019836 | 0.020172 | 0.019264 | 0.021071 | 23642112 | 0.120587 | 13516800 | 6.0478 | 0.5717 | |
| vienna_102081 | full-read | FlatCityBuf | flatcitybuf | median | 0.001916 | 0.001888 | 0.001988 | 0.001862 | 0.002081 | 7503872 | 0.120587 | 13516800 | 62.9368 | 1.8013 | |
| vienna_102081 | full-read | CityParquet | cityparquet | median | 0.014264 | 0.014158 | 0.014393 | 0.014032 | 0.014917 | 33882112 | 0.120587 | 13516800 | 8.4539 | 0.3989 | |
| vienna_102081 | count | CityGML | citygml | median | 0.117978 | 0.117164 | 0.119222 | 0.114926 | 0.130661 | 13565952 | 0.117978 | 13565952 | 1.0000 | 1.0000 | |
| vienna_102081 | count | CityJSON | cityjson | median | 0.020391 | 0.020247 | 0.020687 | 0.019804 | 0.022125 | 63307776 | 0.117978 | 13565952 | 5.7858 | 0.2143 | |
| vienna_102081 | count | CityJSONSeq | cityjsonseq | median | 0.018832 | 0.018657 | 0.019006 | 0.018236 | 0.019580 | 23527424 | 0.117978 | 13565952 | 6.2648 | 0.5766 | |
| vienna_102081 | count | FlatCityBuf | flatcitybuf | median | 0.000030 | 0.000027 | 0.000037 | 0.000025 | 0.000049 | 7290880 | 0.117978 | 13565952 | 3932.6000 | 1.8607 | |
| vienna_102081 | count | CityParquet | cityparquet | median | 0.000260 | 0.000241 | 0.000268 | 0.000212 | 0.000304 | 9175040 | 0.117978 | 13565952 | 453.7615 | 1.4786 | |
| vienna_102081 | bbox-1pct | CityGML | citygml | median | 0.118864 | 0.117816 | 0.119807 | 0.115269 | 0.122466 | 13484032 | 0.118864 | 13484032 | 1.0000 | 1.0000 | |
| vienna_102081 | bbox-1pct | CityJSON | cityjson | median | 0.022591 | 0.022229 | 0.023165 | 0.021598 | 0.024255 | 63897600 | 0.118864 | 13484032 | 5.2616 | 0.2110 | |
| vienna_102081 | bbox-1pct | CityJSONSeq | cityjsonseq | median | 0.019806 | 0.019592 | 0.020039 | 0.019221 | 0.022402 | 27574272 | 0.118864 | 13484032 | 6.0014 | 0.4890 | |
| vienna_102081 | bbox-1pct | FlatCityBuf | flatcitybuf | median | 0.000102 | 0.000099 | 0.000105 | 0.000090 | 0.000161 | 7667712 | 0.118864 | 13484032 | 1165.3333 | 1.7585 | |
| vienna_102081 | bbox-1pct | CityParquet | cityparquet | median | 0.003279 | 0.003219 | 0.003314 | 0.003187 | 0.003505 | 20512768 | 0.118864 | 13484032 | 36.2501 | 0.6573 | |
| vienna_102081 | bbox-5pct | CityGML | citygml | median | 0.117919 | 0.116420 | 0.119292 | 0.115819 | 0.121709 | 13565952 | 0.117919 | 13565952 | 1.0000 | 1.0000 | |
| vienna_102081 | bbox-5pct | CityJSON | cityjson | median | 0.022649 | 0.022251 | 0.023054 | 0.021772 | 0.023964 | 63569920 | 0.117919 | 13565952 | 5.2064 | 0.2134 | |
| vienna_102081 | bbox-5pct | CityJSONSeq | cityjsonseq | median | 0.019617 | 0.019250 | 0.019963 | 0.018990 | 0.020429 | 23674880 | 0.117919 | 13565952 | 6.0111 | 0.5730 | |
| vienna_102081 | bbox-5pct | FlatCityBuf | flatcitybuf | median | 0.000214 | 0.000200 | 0.000225 | 0.000182 | 0.000244 | 7749632 | 0.117919 | 13565952 | 551.0234 | 1.7505 | |
| vienna_102081 | bbox-5pct | CityParquet | cityparquet | median | 0.003692 | 0.003599 | 0.003898 | 0.003343 | 0.008051 | 20840448 | 0.117919 | 13565952 | 31.9391 | 0.6509 | |
| vienna_102081 | bbox-25pct | CityGML | citygml | median | 0.116955 | 0.116458 | 0.117419 | 0.115415 | 0.118883 | 13467648 | 0.116955 | 13467648 | 1.0000 | 1.0000 | |
| vienna_102081 | bbox-25pct | CityJSON | cityjson | median | 0.022765 | 0.022520 | 0.023021 | 0.022101 | 0.023447 | 63225856 | 0.116955 | 13467648 | 5.1375 | 0.2130 | |
| vienna_102081 | bbox-25pct | CityJSONSeq | cityjsonseq | median | 0.019782 | 0.019594 | 0.020136 | 0.019407 | 0.023702 | 23822336 | 0.116955 | 13467648 | 5.9122 | 0.5653 | |
| vienna_102081 | bbox-25pct | FlatCityBuf | flatcitybuf | median | 0.000594 | 0.000581 | 0.000609 | 0.000558 | 0.000636 | 7995392 | 0.116955 | 13467648 | 196.8939 | 1.6844 | |
| vienna_102081 | bbox-25pct | CityParquet | cityparquet | median | 0.003798 | 0.003769 | 0.003853 | 0.003653 | 0.004052 | 24264704 | 0.116955 | 13467648 | 30.7938 | 0.5550 | |
| vienna_102081 | attr-filter | CityGML | citygml | median | 0.116497 | 0.115658 | 0.117737 | 0.114476 | 0.125301 | 13615104 | 0.116497 | 13615104 | 1.0000 | 1.0000 | |
| vienna_102081 | attr-filter | CityJSON | cityjson | median | 0.020514 | 0.020199 | 0.020675 | 0.019874 | 0.021359 | 63406080 | 0.116497 | 13615104 | 5.6789 | 0.2147 | |
| vienna_102081 | attr-filter | CityJSONSeq | cityjsonseq | median | 0.019036 | 0.018727 | 0.019407 | 0.018465 | 0.025052 | 27918336 | 0.116497 | 13615104 | 6.1198 | 0.4877 | |
| vienna_102081 | attr-filter | FlatCityBuf | flatcitybuf | median | 0.001481 | 0.001444 | 0.001513 | 0.001405 | 0.001773 | 7782400 | 0.116497 | 13615104 | 78.6610 | 1.7495 | |
| vienna_102081 | attr-filter | CityParquet | cityparquet | median | 0.000503 | 0.000496 | 0.000544 | 0.000434 | 0.000676 | 11223040 | 0.116497 | 13615104 | 231.6044 | 1.2131 | |
| vienna_102081 | attr-stats | CityGML | citygml | median | 0.115860 | 0.115122 | 0.116971 | 0.114185 | 0.134100 | 13418496 | 0.115860 | 13418496 | 1.0000 | 1.0000 | |
| vienna_102081 | attr-stats | CityJSON | cityjson | median | 0.020482 | 0.020167 | 0.021231 | 0.019618 | 0.023592 | 63340544 | 0.115860 | 13418496 | 5.6567 | 0.2118 | |
| vienna_102081 | attr-stats | CityJSONSeq | cityjsonseq | median | 0.019049 | 0.018765 | 0.019173 | 0.018422 | 0.019615 | 24330240 | 0.115860 | 13418496 | 6.0822 | 0.5515 | |
| vienna_102081 | attr-stats | FlatCityBuf | flatcitybuf | median | 0.001587 | 0.001435 | 0.001969 | 0.001362 | 0.003108 | 7553024 | 0.115860 | 13418496 | 73.0057 | 1.7766 | |
| vienna_102081 | attr-stats | CityParquet | cityparquet | median | 0.000341 | 0.000331 | 0.000354 | 0.000298 | 0.000418 | 10174464 | 0.115860 | 13418496 | 339.7654 | 1.3188 | |
| vienna_102081 | id-10pct | CityGML | citygml | median | 0.025398 | 0.025187 | 0.025963 | 0.025053 | 0.028764 | 10649600 | 0.025398 | 10649600 | 1.0000 | 1.0000 | |
| vienna_102081 | id-10pct | CityJSON | cityjson | median | 0.020314 | 0.020057 | 0.020661 | 0.019689 | 0.022804 | 63324160 | 0.025398 | 10649600 | 1.2503 | 0.1682 | |
| vienna_102081 | id-10pct | CityJSONSeq | cityjsonseq | median | 0.003281 | 0.003233 | 0.003352 | 0.003183 | 0.003756 | 21987328 | 0.025398 | 10649600 | 7.7409 | 0.4844 | |
| vienna_102081 | id-10pct | FlatCityBuf | flatcitybuf | median | 0.001466 | 0.001386 | 0.001702 | 0.001312 | 0.002210 | 7585792 | 0.025398 | 10649600 | 17.3247 | 1.4039 | |
| vienna_102081 | id-10pct | CityParquet | cityparquet | median | 0.004342 | 0.004307 | 0.004446 | 0.004217 | 0.005259 | 25018368 | 0.025398 | 10649600 | 5.8494 | 0.4257 | |
| vienna_102081 | id-50pct | CityGML | citygml | median | 0.061034 | 0.060727 | 0.061243 | 0.060327 | 0.062108 | 10829824 | 0.061034 | 10829824 | 1.0000 | 1.0000 | |
| vienna_102081 | id-50pct | CityJSON | cityjson | median | 0.020182 | 0.019759 | 0.020484 | 0.019301 | 0.020993 | 63275008 | 0.061034 | 10829824 | 3.0242 | 0.1712 | |
| vienna_102081 | id-50pct | CityJSONSeq | cityjsonseq | median | 0.009673 | 0.009509 | 0.010006 | 0.009200 | 0.010141 | 26017792 | 0.061034 | 10829824 | 6.3097 | 0.4162 | |
| vienna_102081 | id-50pct | FlatCityBuf | flatcitybuf | median | 0.000172 | 0.000156 | 0.000180 | 0.000142 | 0.000222 | 7569408 | 0.061034 | 10829824 | 354.8488 | 1.4307 | |
| vienna_102081 | id-50pct | CityParquet | cityparquet | median | 0.003374 | 0.003280 | 0.003465 | 0.003170 | 0.003790 | 19398656 | 0.061034 | 10829824 | 18.0895 | 0.5583 | |
| vienna_102081 | id-90pct | CityGML | citygml | median | 0.103905 | 0.103702 | 0.105445 | 0.103221 | 0.106603 | 13434880 | 0.103905 | 13434880 | 1.0000 | 1.0000 | |
| vienna_102081 | id-90pct | CityJSON | cityjson | median | 0.019993 | 0.019816 | 0.020186 | 0.019418 | 0.021078 | 62898176 | 0.103905 | 13434880 | 5.1971 | 0.2136 | |
| vienna_102081 | id-90pct | CityJSONSeq | cityjsonseq | median | 0.016809 | 0.016573 | 0.016995 | 0.016270 | 0.017613 | 27246592 | 0.103905 | 13434880 | 6.1815 | 0.4931 | |
| vienna_102081 | id-90pct | FlatCityBuf | flatcitybuf | median | 0.000189 | 0.000185 | 0.000194 | 0.000163 | 0.000218 | 7602176 | 0.103905 | 13434880 | 549.7619 | 1.7672 | |
| vienna_102081 | id-90pct | CityParquet | cityparquet | median | 0.003423 | 0.003379 | 0.003478 | 0.003212 | 0.003751 | 19382272 | 0.103905 | 13434880 | 30.3550 | 0.6932 | |
| vienna_102081 | id-miss | CityGML | citygml | median | 0.116910 | 0.115374 | 0.118242 | 0.114461 | 0.123674 | 13565952 | 0.116910 | 13565952 | 1.0000 | 1.0000 | |
| vienna_102081 | id-miss | CityJSON | cityjson | median | 0.021002 | 0.020646 | 0.021375 | 0.020374 | 0.023220 | 63324160 | 0.116910 | 13565952 | 5.5666 | 0.2142 | |
| vienna_102081 | id-miss | CityJSONSeq | cityjsonseq | median | 0.018879 | 0.018479 | 0.019093 | 0.018201 | 0.019775 | 27443200 | 0.116910 | 13565952 | 6.1926 | 0.4943 | |
| vienna_102081 | id-miss | FlatCityBuf | flatcitybuf | median | 0.001436 | 0.001401 | 0.001468 | 0.001288 | 0.001497 | 7569408 | 0.116910 | 13565952 | 81.4136 | 1.7922 | |
| vienna_102081 | id-miss | CityParquet | cityparquet | median | 0.000343 | 0.000333 | 0.000350 | 0.000312 | 0.000420 | 10158080 | 0.116910 | 13565952 | 340.8455 | 1.3355 | |
| rotterdam_delfshaven | full-read | CityGML | citygml | median | 0.098339 | 0.097436 | 0.100567 | 0.095213 | 0.120376 | 59998208 | 0.098339 | 59998208 | 1.0000 | 1.0000 | |
| rotterdam_delfshaven | full-read | CityJSON | cityjson | median | 0.015173 | 0.014808 | 0.015540 | 0.014659 | 0.017045 | 53837824 | 0.098339 | 59998208 | 6.4812 | 1.1144 | |
| rotterdam_delfshaven | full-read | CityJSONSeq | cityjsonseq | median | 0.013774 | 0.013633 | 0.013956 | 0.013447 | 0.020266 | 20594688 | 0.098339 | 59998208 | 7.1395 | 2.9133 | |
| rotterdam_delfshaven | full-read | FlatCityBuf | flatcitybuf | median | 0.001120 | 0.001108 | 0.001143 | 0.001090 | 0.001161 | 7503872 | 0.098339 | 59998208 | 87.8027 | 7.9956 | |
| rotterdam_delfshaven | full-read | CityParquet | cityparquet | median | 0.004414 | 0.004364 | 0.004554 | 0.004261 | 0.004820 | 24625152 | 0.098339 | 59998208 | 22.2789 | 2.4365 | |
| rotterdam_delfshaven | count | CityGML | citygml | median | 0.096254 | 0.095721 | 0.096733 | 0.095051 | 0.101391 | 59506688 | 0.096254 | 59506688 | 1.0000 | 1.0000 | |
| rotterdam_delfshaven | count | CityJSON | cityjson | median | 0.014276 | 0.014013 | 0.014668 | 0.013576 | 0.017264 | 53526528 | 0.096254 | 59506688 | 6.7424 | 1.1117 | |
| rotterdam_delfshaven | count | CityJSONSeq | cityjsonseq | median | 0.013484 | 0.013404 | 0.013603 | 0.013211 | 0.013756 | 16777216 | 0.096254 | 59506688 | 7.1384 | 3.5469 | |
| rotterdam_delfshaven | count | FlatCityBuf | flatcitybuf | median | 0.000024 | 0.000023 | 0.000028 | 0.000022 | 0.000032 | 7290880 | 0.096254 | 59506688 | 4010.5833 | 8.1618 | |
| rotterdam_delfshaven | count | CityParquet | cityparquet | median | 0.000233 | 0.000220 | 0.000237 | 0.000208 | 0.000257 | 9256960 | 0.096254 | 59506688 | 413.1073 | 6.4283 | |
| rotterdam_delfshaven | bbox-1pct | CityGML | citygml | median | 0.097097 | 0.096674 | 0.097762 | 0.095430 | 0.099496 | 59572224 | 0.097097 | 59572224 | 1.0000 | 1.0000 | |
| rotterdam_delfshaven | bbox-1pct | CityJSON | cityjson | median | 0.015881 | 0.015573 | 0.016443 | 0.015237 | 0.017290 | 53739520 | 0.097097 | 59572224 | 6.1140 | 1.1085 | |
| rotterdam_delfshaven | bbox-1pct | CityJSONSeq | cityjsonseq | median | 0.013817 | 0.013773 | 0.013927 | 0.013555 | 0.016832 | 16924672 | 0.097097 | 59572224 | 7.0274 | 3.5198 | |
| rotterdam_delfshaven | bbox-1pct | FlatCityBuf | flatcitybuf | median | 0.000068 | 0.000064 | 0.000072 | 0.000062 | 0.000084 | 7618560 | 0.097097 | 59572224 | 1427.8971 | 7.8194 | |
| rotterdam_delfshaven | bbox-1pct | CityParquet | cityparquet | median | 0.001987 | 0.001965 | 0.002007 | 0.001947 | 0.002050 | 16629760 | 0.097097 | 59572224 | 48.8661 | 3.5823 | |
| rotterdam_delfshaven | bbox-5pct | CityGML | citygml | median | 0.096917 | 0.096397 | 0.098149 | 0.094215 | 0.099996 | 59277312 | 0.096917 | 59277312 | 1.0000 | 1.0000 | |
| rotterdam_delfshaven | bbox-5pct | CityJSON | cityjson | median | 0.015902 | 0.015466 | 0.016168 | 0.014812 | 0.017251 | 54149120 | 0.096917 | 59277312 | 6.0946 | 1.0947 | |
| rotterdam_delfshaven | bbox-5pct | CityJSONSeq | cityjsonseq | median | 0.013879 | 0.013768 | 0.014040 | 0.013677 | 0.015860 | 16531456 | 0.096917 | 59277312 | 6.9830 | 3.5857 | |
| rotterdam_delfshaven | bbox-5pct | FlatCityBuf | flatcitybuf | median | 0.000126 | 0.000119 | 0.000142 | 0.000113 | 0.000170 | 7700480 | 0.096917 | 59277312 | 769.1825 | 7.6979 | |
| rotterdam_delfshaven | bbox-5pct | CityParquet | cityparquet | median | 0.002009 | 0.001998 | 0.002054 | 0.001960 | 0.002189 | 16613376 | 0.096917 | 59277312 | 48.2414 | 3.5680 | |
| rotterdam_delfshaven | bbox-25pct | CityGML | citygml | median | 0.097028 | 0.096244 | 0.098292 | 0.095134 | 0.114533 | 59637760 | 0.097028 | 59637760 | 1.0000 | 1.0000 | |
| rotterdam_delfshaven | bbox-25pct | CityJSON | cityjson | median | 0.015406 | 0.015273 | 0.015556 | 0.014960 | 0.024616 | 53755904 | 0.097028 | 59637760 | 6.2981 | 1.1094 | |
| rotterdam_delfshaven | bbox-25pct | CityJSONSeq | cityjsonseq | median | 0.013789 | 0.013718 | 0.013862 | 0.013509 | 0.014739 | 16531456 | 0.097028 | 59637760 | 7.0366 | 3.6075 | |
| rotterdam_delfshaven | bbox-25pct | FlatCityBuf | flatcitybuf | median | 0.000388 | 0.000378 | 0.000397 | 0.000354 | 0.000435 | 7684096 | 0.097028 | 59637760 | 250.0722 | 7.7612 | |
| rotterdam_delfshaven | bbox-25pct | CityParquet | cityparquet | median | 0.002117 | 0.002101 | 0.002134 | 0.002048 | 0.002164 | 18038784 | 0.097028 | 59637760 | 45.8328 | 3.3061 | |
| rotterdam_delfshaven | attr-filter | CityGML | citygml | median | 0.096266 | 0.095661 | 0.097610 | 0.094616 | 0.106694 | 59293696 | 0.096266 | 59293696 | 1.0000 | 1.0000 | |
| rotterdam_delfshaven | attr-filter | CityJSON | cityjson | median | 0.014922 | 0.014356 | 0.015387 | 0.013897 | 0.017521 | 54018048 | 0.096266 | 59293696 | 6.4513 | 1.0977 | |
| rotterdam_delfshaven | attr-filter | CityJSONSeq | cityjsonseq | median | 0.013619 | 0.013418 | 0.013820 | 0.013312 | 0.015050 | 16826368 | 0.096266 | 59293696 | 7.0685 | 3.5239 | |
| rotterdam_delfshaven | attr-filter | FlatCityBuf | flatcitybuf | median | 0.000534 | 0.000522 | 0.000546 | 0.000510 | 0.000587 | 7847936 | 0.096266 | 59293696 | 180.2734 | 7.5553 | |
| rotterdam_delfshaven | attr-filter | CityParquet | cityparquet | median | 0.000392 | 0.000383 | 0.000418 | 0.000368 | 0.000498 | 11206656 | 0.096266 | 59293696 | 245.5765 | 5.2909 | |
| rotterdam_delfshaven | attr-stats | CityGML | citygml | median | 0.097489 | 0.096255 | 0.108667 | 0.095415 | 0.334788 | 60424192 | 0.097489 | 60424192 | 1.0000 | 1.0000 | |
| rotterdam_delfshaven | attr-stats | CityJSON | cityjson | median | 0.014361 | 0.014119 | 0.014968 | 0.013439 | 0.021590 | 54132736 | 0.097489 | 60424192 | 6.7885 | 1.1162 | |
| rotterdam_delfshaven | attr-stats | CityJSONSeq | cityjsonseq | median | 0.013605 | 0.013487 | 0.013780 | 0.013220 | 0.015271 | 16777216 | 0.097489 | 60424192 | 7.1657 | 3.6016 | |
| rotterdam_delfshaven | attr-stats | FlatCityBuf | flatcitybuf | median | 0.000961 | 0.000951 | 0.000975 | 0.000932 | 0.001010 | 7503872 | 0.097489 | 60424192 | 101.4454 | 8.0524 | |
| rotterdam_delfshaven | attr-stats | CityParquet | cityparquet | median | 0.000289 | 0.000282 | 0.000296 | 0.000275 | 0.000311 | 10223616 | 0.097489 | 60424192 | 337.3322 | 5.9103 | |
| rotterdam_delfshaven | id-10pct | CityGML | citygml | median | 0.026510 | 0.025978 | 0.027074 | 0.025470 | 0.036980 | 58638336 | 0.026510 | 58638336 | 1.0000 | 1.0000 | |
| rotterdam_delfshaven | id-10pct | CityJSON | cityjson | median | 0.014503 | 0.014174 | 0.014634 | 0.013807 | 0.021226 | 54165504 | 0.026510 | 58638336 | 1.8279 | 1.0826 | |
| rotterdam_delfshaven | id-10pct | CityJSONSeq | cityjsonseq | median | 0.002365 | 0.002306 | 0.002411 | 0.002239 | 0.002879 | 15712256 | 0.026510 | 58638336 | 11.2093 | 3.7320 | |
| rotterdam_delfshaven | id-10pct | FlatCityBuf | flatcitybuf | median | 0.000555 | 0.000546 | 0.000560 | 0.000530 | 0.000605 | 7553024 | 0.026510 | 58638336 | 47.7658 | 7.7636 | |
| rotterdam_delfshaven | id-10pct | CityParquet | cityparquet | median | 0.002954 | 0.002822 | 0.003066 | 0.002727 | 0.003498 | 19759104 | 0.026510 | 58638336 | 8.9743 | 2.9677 | |
| rotterdam_delfshaven | id-50pct | CityGML | citygml | median | 0.057211 | 0.056232 | 0.058177 | 0.055028 | 0.062744 | 59047936 | 0.057211 | 59047936 | 1.0000 | 1.0000 | |
| rotterdam_delfshaven | id-50pct | CityJSON | cityjson | median | 0.014355 | 0.014054 | 0.014917 | 0.013602 | 0.016090 | 53755904 | 0.057211 | 59047936 | 3.9854 | 1.0984 | |
| rotterdam_delfshaven | id-50pct | CityJSONSeq | cityjsonseq | median | 0.006966 | 0.006937 | 0.007055 | 0.006864 | 0.007232 | 16171008 | 0.057211 | 59047936 | 8.2129 | 3.6515 | |
| rotterdam_delfshaven | id-50pct | FlatCityBuf | flatcitybuf | median | 0.000697 | 0.000689 | 0.000711 | 0.000669 | 0.000806 | 7553024 | 0.057211 | 59047936 | 82.0818 | 7.8178 | |
| rotterdam_delfshaven | id-50pct | CityParquet | cityparquet | median | 0.002842 | 0.002757 | 0.003009 | 0.002691 | 0.003240 | 19775488 | 0.057211 | 59047936 | 20.1305 | 2.9859 | |
| rotterdam_delfshaven | id-90pct | CityGML | citygml | median | 0.089631 | 0.088901 | 0.090333 | 0.087666 | 0.153689 | 59834368 | 0.089631 | 59834368 | 1.0000 | 1.0000 | |
| rotterdam_delfshaven | id-90pct | CityJSON | cityjson | median | 0.013730 | 0.013472 | 0.013844 | 0.013295 | 0.014077 | 53428224 | 0.089631 | 59834368 | 6.5281 | 1.1199 | |
| rotterdam_delfshaven | id-90pct | CityJSONSeq | cityjsonseq | median | 0.012132 | 0.011921 | 0.012281 | 0.011781 | 0.015487 | 18415616 | 0.089631 | 59834368 | 7.3880 | 3.2491 | |
| rotterdam_delfshaven | id-90pct | FlatCityBuf | flatcitybuf | median | 0.000287 | 0.000281 | 0.000299 | 0.000267 | 0.000329 | 7553024 | 0.089631 | 59834368 | 312.3031 | 7.9219 | |
| rotterdam_delfshaven | id-90pct | CityParquet | cityparquet | median | 0.002833 | 0.002807 | 0.002870 | 0.002779 | 0.002946 | 19726336 | 0.089631 | 59834368 | 31.6382 | 3.0332 | |
| rotterdam_delfshaven | id-miss | CityGML | citygml | median | 0.097790 | 0.096784 | 0.098670 | 0.095791 | 0.103322 | 60063744 | 0.097790 | 60063744 | 1.0000 | 1.0000 | |
| rotterdam_delfshaven | id-miss | CityJSON | cityjson | median | 0.014294 | 0.013812 | 0.015273 | 0.013336 | 0.017490 | 53460992 | 0.097790 | 60063744 | 6.8413 | 1.1235 | |
| rotterdam_delfshaven | id-miss | CityJSONSeq | cityjsonseq | median | 0.013348 | 0.013285 | 0.013598 | 0.013019 | 0.014021 | 16744448 | 0.097790 | 60063744 | 7.3262 | 3.5871 | |
| rotterdam_delfshaven | id-miss | FlatCityBuf | flatcitybuf | median | 0.000956 | 0.000946 | 0.000970 | 0.000930 | 0.001082 | 7569408 | 0.097790 | 60063744 | 102.2908 | 7.9351 | |
| rotterdam_delfshaven | id-miss | CityParquet | cityparquet | median | 0.000309 | 0.000301 | 0.000321 | 0.000290 | 0.000352 | 10256384 | 0.097790 | 60063744 | 316.4725 | 5.8562 | |
Coverage
{
"families": {
"formats": {
"present": true,
"metrics": [
"attr-filter",
"attr-stats",
"bbox-1pct",
"bbox-25pct",
"bbox-5pct",
"count",
"full-read",
"id-10pct",
"id-50pct",
"id-90pct",
"id-miss"
]
},
"sizes": {
"present": true,
"metrics": [
"bytes"
]
},
"bloom": {
"present": false,
"metrics": []
},
"databases": {
"present": false,
"metrics": []
}
}
}Measurement caveats
- **Counting granularity differs by scenario, not just by format.**
CityParquet's `count`/`full-read` count **one row per CityObject** —
parents _and_ children each get a row — and `cityjson` matches that grain
(its `CityObjects` map is flat: a `Building` and its `BuildingPart`s are
sibling entries linked only by `parents`/`children`). `cityjsonseq`,
`flatcitybuf` and `citygml` instead count top-level **features/members**
for `count` and the `full-read` `result_count` (a CityJSONSeq/FCB feature
bundles one top-level CityObject with all its children inline, exactly as
a CityGML `cityObjectMember` nests its `BuildingPart`s). So the `count`
and `full-read` rows split into **two grains**:
| grain | formats |
| ---------------------------------------------------------- | --------------------------------------- |
| one row per **CityObject** (children counted separately) | `cityparquet`, `cityjson` |
| one row per **top-level feature/member** (children inline) | `citygml`, `cityjsonseq`, `flatcitybuf` |
The `full-read` totals the coordinator compares (`objects`, `geometries`,
`semantic_faces`) are per CityObject in every format; only its
`result_count` keeps the feature grain. `bbox-query`, `attr-filter`,
`attr-stats` and `id-lookup` are **CityObject-granular in every format**
— `citygml`/`cityjsonseq`/`flatcitybuf` flatten to per-CityObject
matching for these four scenarios (`flatcitybuf` because that is what its B+-tree
attribute index naturally returns per entry: `fcb_core` indexes every
value of a feature's `city_objects` map, not only the root object, so on
Vienna an indexed `attr-filter` returns 600 entries from 307 features,
and the raw-accessor walks of Caveat 32 count the same way; the two
parsing formats by explicit choice, to match) — so these four are
directly comparable across every format; the `count` and `full-read`
`result_count` are not.
Empirically: `lod3_railway.city.json` is 121 CityObjects / 38 top-level
features; `delft.city.jsonl`'s `object_type == "BuildingPart"` count is
1116 CityObjects (out of 2231 total CityObjects / 1115 features);
`railway_lod3_fragment.gml` is 6 CityObjects / 4 members. Each of those is
asserted in the runners' own tests, not merely claimed here.
**What "an object's bbox intersects the window" means, in every format.**
An object's box is the min/max over every vertex referenced by a geometry
in its **subtree** — its own geometries and every descendant's, reached
through `children` — so a `Building` with no geometry of its own matches a
window its `BuildingPart`s intersect, and a `CityObjectGroup` matches
through its members. This is the subtree union the CityParquet `bbox`
column stores (the specification's "Spatial metadata"), and every runner
computes the same union: `cityjson` over the shared vertices, `citygml`
and `cityjsonseq` over each feature's own vertices, and `flatcitybuf`
for every CityObject of each feature its R-tree hits. Every format
therefore returns the identifiers of the same matching CityObjects, and
the coordinator compares their digest (Caveat 2). A box intersects the
window when it overlaps it on every axis, edges included. An object with
no geometry anywhere in its subtree has no box and matches nothing.
The CityParquet `bbox` column also covers an object's declared
`geographicalExtent`, which no runner reads (FlatCityBuf's R-tree holds
the boxes `fcb ser` computed), so on a dataset that declares one the
column can reach beyond the vertex box. On this corpus Zurich and
Montréal declare one on every object. Montréal's reaches beyond the
vertex box in `x`/`y` on 1,544 objects, by at most 1.5 µm. Zurich's
reaches beyond it on 108,396 objects, by at most its 1 mm quantisation.
The untied windows guarantee only that every edge lies more than one
quantisation step from every `bbox` edge (Caveat 21), which for Zurich is
the same 1 mm, so the guarantee alone does not keep a window edge out of
the band between an object's vertex box and its `bbox`. The coordinator's
check does: a window edge inside that band would make `cityjson`
disagree with `cityparquet`, and the run would fail. On the current
Zurich artefacts no window edge is closer than 3 mm to a `bbox` edge.
- **Selectivity's denominator is the CityObject total, for every format.**
The four CityObject-granular scenarios (`bbox-query`/`attr-filter`/
`attr-stats`/`id-lookup`) divide by the **dataset-global CityObject
total** — the same number as CityParquet's own `count` — as a single
shared denominator across every format, so their selectivity is always
in `(0, 1]` and directly comparable format to format. `count` and
`full-read` carry no selectivity.
**A count that disagrees fails the run.** Once every format has run, the
coordinator checks each row's `result_count` at its own counting level.
`count` and `full-read` must equal the CityParquet table's CityObjects or
its features (root objects, `id == feature_id`); each `bbox-*` window
must select exactly the CityObjects whose box intersects it as the
CityParquet `bbox` column says (the reference count is in the parameter
sidecar, `windows[].objects`); `attr-filter` must match the predicate's
`matched`. Beyond the counts, the coordinator compares what each format
RETURNED (Caveat 42). `attr-stats` and each `id-*` probe have no reference and must
agree across every format. A disagreeing row is tagged `count-mismatch`
in `notes`, the CSV is written anyway, and the coordinator exits
non-zero naming the scenario, the formats and the counts; a clean run
prints `cross-format consistency OK` on stderr.
- **`full-read` returns the same thing from every format, by different
work.** Every format returns every record with all its fields in its
native form, and every format visits every coordinate as a real
coordinate: CityGML streams its members through the library's CityGML
reader, which decodes every `gml:pos`/`posList`, resolves every
`xlink:href` surface reference and rebuilds a feature-local vertex pool;
plain CityJSON parses one whole document and resolves every boundary leaf
through its shared vertex array and `transform`; CityJSONSeq parses every
line and resolves each feature's own vertices through the header
`transform`; FlatCityBuf converts its stored integer vertices to real
coordinates; CityParquet walks its Arrow arrays and WKB in place. Each is
that format's own full-read cost, reported per format and never
normalised into a shared unit of work. The coordinator checks that the
five agree on the CityObjects, geometries, semantic faces and extent they
visited (Caveat 42).
- **FlatCityBuf's `bbox-query` is 2D.** Its spatial R-tree indexes x/y
only; the query window's z component is silently dropped when querying
FCB (the window itself is still constructed with a full z range, as it
is for every other format, but FCB's own index simply has no z
dimension to test against). The index's hits are therefore a superset
of the matches, never a subset, and the runner's own 3D test of every
CityObject in a hit feature removes the extra ones, so FlatCityBuf's
`result_count` equals every other format's.
The R-tree also indexes FEATURES, not CityObjects: its entry is a
feature's 2D box. The runner therefore reads each hit feature once and
tests every CityObject in it against that object's own 3D subtree box,
so the returned CityObjects agree with every other format, but a hit
feature's non-matching CityObjects (and every feature whose 2D box
intersects while no 3D box does) are read and tested as well. That
extra reading is the cost of a per-feature 2D index, and it is inside
FlatCityBuf's timing.
- **DuckDB over CityParquet is measured by the database comparison, not
here — and an ad-hoc DuckDB query over a package needs one setting.**
This family times each format through its own reader in a
`cityparquet-readbench` child. DuckDB reading the same Hilbert-ordered
package is a query engine rather than a format, so
`benchmark/databases/README.md` measures it beside cjdb and 3DCityDB,
with its own query suite, thread configurations and count cross-check.
Its rows are not a sixth format and must not be set beside this
family's rows as if they were.
DuckDB reads the package with `read_parquet()` straight over a
`cityparquet-rs`-written package — full geometry, every LoD column.
**However**: the package's WKB geometry columns carry GeoParquet "geo"
file metadata, and DuckDB's spatial extension (autoloaded) eagerly
tries to decode them into its own native `GEOMETRY` type the instant a
query references the column — even a bare `SELECT` with no function
applied — and that native decode does not support the multi-surface/
solid WKB shapes our LoD1.2/LoD1.3/LoD2 geometries use, failing with
`Invalid Input Error: Unsupported geometry type in WKB`. The database
family sets `enable_geoparquet_conversion=false` on every connection
(`benchmark/databases/src/citybench/systems/duckdb_cp.py`,
`citybench/params.py`), which makes
those columns read back as plain `BLOB`. **Anyone running an ad-hoc
DuckDB query against a `cityparquet-rs` package that touches a geometry
column needs the same setting**, or will hit the identical error. The
LoD0 footprint is the exception: it is written with Parquet's own
GEOMETRY logical type, which DuckDB decodes to `GEOMETRY` whatever the
setting.
- **A DuckDB memory figure and a format memory figure measure different
processes.** Here `peak_heap_bytes` and `peak_rss_bytes` belong to one
fresh `cityparquet-readbench` child per sample (see the CSV contract
above). The database comparison reports no `peak_heap_bytes` for DuckDB,
and its `peak_rss_bytes` samples the process executing the query — for
DuckDB the harness's own Python process, which hosts the engine
in-process, keeps one connection across the scenario sequence and so
carries the interpreter, the engine's idle baseline and memory retained
from earlier scenarios (the `peak_rss_bytes` scope caveat in
`benchmark/databases/README.md`). Quote each within its own family; a
ratio between the two is a ratio of different scopes.
- **Warm vs cold — never silently mixed.** The headline numbers everywhere
in this document and in `benchmark/runs/formats/results/*.csv` are warm-cache
statistics (the summaries' median by default, the mean under
`--statistic mean`); the single `cold`-tagged row per format (see "Warm vs cold"
above) is a distinct, separately-reported measurement, always
`full-read` only, never averaged into or compared unlabelled against the
warm rows.
- **Sub-millisecond deltas are noise; single-threaded reads are pinned.**
As in `benchmark/formats/README.md`'s own methodology, deltas under roughly 10 ms at
`repeat = 7` are within scheduler/filesystem-cache noise and are not
cited as a finding by themselves. That threshold applies to a run at
`repeat = 7`; the default is 25, and a run at 25 samples is read against
its own spread (the interquartile range under
the median default), not against this figure. Every format's reads here run
single-threaded (no Parquet multi-threaded row-group decode) — a
deliberate, disclosed choice so timing differences reflect the
format/mechanism, not thread-count parallelism a production deployment
might or might not enable. Parallel query execution is measured in the
database comparison, whose rows carry `threads=single` or
`threads=parallel`.
- **`id-lookup` is FOUR rows per format, and the three hit rows are not
comparable across formats as raw times.** A single target made the
published number a function of where that one id happened to sit in the
file — a property of the sample, not of the format. Each format is now
measured against four targets, tagged in `notes`:
| tag | target |
| ---------- | -------------------------------------- |
| `id-10pct` | the id at 10% of the canonical order |
| `id-50pct` | the id at 50% |
| `id-90pct` | the id at 90% |
| `id-miss` | an id verified absent from the dataset |
The **canonical order is the CityJSONSeq stream**, because
`readbench_prepare.sh` cuts the FlatCityBuf and CityParquet artefacts
from that one file. Each probe is verified present in both the
CityParquet table and the CityGML artefact before it is used; a probe that
fails verification is replaced by the nearest feature that passes and the
row carries `id-substituted`.
**Every format now takes the best mechanism its encoding affords**, which
is the change that makes the column mean one thing:
- `cityjsonseq` and `citygml` stop at the hit. CityGML did not
before, because its skipped-member guard (Caveat 12) is only
authoritative at EOF; that guard now rides on the **untimed `count`
pass** the coordinator runs per format per dataset before any scenario,
whose failure aborts the run, so a document with unmapped members still
cannot reach a published row. An `id-lookup` **miss** drains by
necessity and still consults the guard directly.
- `cityjson` cannot exit early in principle: a whole-document CityJSON
must be parsed in one piece before any object is addressable, so its
four rows are near-identical and position-independent.
- `flatcitybuf` tries its B+-tree first and falls back to a full walk;
see the new Caveat 19 for why the fallback is structural.
- `cityparquet` applies `id` as a `RowFilter`, which evaluates the whole
id column regardless of position.
**`id-miss` is the row to compare across formats.** It is position-free,
and it is what actually separates a format carrying an id index from one
that does not.
- **The timing columns are end-to-end read latency, not isolated query compute.** The
timed window is the whole per-format `run()` call, which INCLUDES opening
the file, reading Parquet/FlatCityBuf metadata or the CityJSONSeq header,
and (for CityParquet full-read/id-lookup) a metadata open — not only the
query kernel. This is deliberate and consistent across every format (each
pays its own open+read), and it is what a caller issuing a one-shot query
against a file actually experiences; but it means a sub-millisecond
the time for a metadata-only scenario (`count`) is dominated by file-open,
not query work. Interpret the numbers as end-to-end single-query latency,
not a pure in-memory kernel micro-benchmark.
- **FlatCityBuf index assumptions and compression scope (known limitations).** The
FlatCityBuf runner uses FCB's native indexes (`select_query` for bbox,
`select_attr_query` for the attribute filter), which requires the `.fcb`
to carry a spatial index (default) and an attribute index (`fcb ser -A`,
which `readbench-prepare` always passes; `id` is never indexed, Caveat
19). If the attribute index cannot answer, the runner falls back to a
full walk and **says so in the CSV `notes`**: `no-attr-index` when the
column carries no B+-tree at all, `attr-index-failed` when the index
query errored, returned a hit list longer than the file's feature
count, or, over HTTP, returned no hits. The second case is a known
truncation in `fcb_core` 0.7.6: its attribute-index iterator stops
after as many entries as the file has features, while the index holds
one entry per matching CityObject, so a predicate that matches more
CityObjects than there are features would otherwise lose matches
silently. The third is a defect in the same library's HTTP index
reader, which answers no hits for a key that names 4,096 or more
features (see "FlatCityBuf's HTTP attribute index drops every key that
names 4,096 or more features" later in this section), so the HTTP arm
verifies an empty answer by the full walk; a walk that also finds
nothing is a verified empty result. An index-vs-scan measurement is
therefore never mislabelled. That fallback is a raw-flatbuffer walk, not a
CityJSON conversion — see Caveat 32. No artefact is read through an
external compression layer: `citygml`, `cityjson` and `cityjsonseq` are
read as uncompressed text and `flatcitybuf` as an uncompressed file, so
their sizes and read times are those of the uncompressed files, while
CityParquet's only compression is the Parquet page
compression its writer applies by default. A gzipped text file is not
one of the five formats, and no number here describes one. External review
(Codex, 2026-07-08) confirmed the query primitives, bbox prune + row-level
filter, and allocator placement correct; its two flagged "dictionary"
criticals were verified FALSE POSITIVES — `TypedDictionaryArray::value(i)`
resolves the row's key, and an `attr-filter(object_type)` run
over 2231 rows with ~4 distinct types would have panicked at row 4 had the
alleged raw-index reading been real.
- **`citygml` measures THIS REPOSITORY'S reader, not CityGML's ceiling.**
This is the single most important caveat on the `citygml` row, and it cuts
both ways.
The row answers exactly one question: _what does it cost to answer this
query against the format the data actually ships in, using the same
codebase as every other row?_ It is **not** a claim about what CityGML
could achieve in principle. A different parser — a streaming SAX filter
tuned to one query, an XML database with a pre-built index, a commercial
CityGML engine — would give different numbers, and nothing measured here
bounds them. What _is_ structural rather than implementation-specific is
the absence of an index: a published `.gml` carries no offsets, no object
directory and no spatial or attribute tree, so _any_ reader must traverse
the document to answer any of the six scenarios. The constant factor is
ours; the linear term is the format's.
Two further disclosures about that row, neither folded silently into it:
- **The appearance pre-pass is skipped.** `FeatureReader::open` re-reads
the whole document up front to index CityModel-level appearance; not one
of the six scenarios consults appearance, so the runner uses
`open_without_appearance`. On a real 117 MB PLATEAU tile that pre-pass
was ~35–45% of `count`'s elapsed time and ~20× its peak heap — both
published CSV columns, and both measuring this harness rather than
CityGML. Leaving it in would have inflated the `citygml` row with work
no scenario asks for.
- **The row describes this reader's supported profile of CityGML** — see
Caveat 16 for which modules that rules out of the corpus, and why a
document exercising the gap is refused outright rather than measured.
- **`cityjson` and `cityjsonseq` resolve coordinates differently in
`full-read`.** Both resolve every boundary leaf into a real-world
coordinate, but `cityjson` resolves it through the one document-level
`vertices` array and `transform`, while `cityjsonseq` resolves it
through the feature's own local vertices and the header `transform`. On
the `lod3_railway` fixture `cityjson` performs **245,137 leaf
resolutions against 73,554 unique vertices** (the leaves outnumber the
vertices more than threefold, so this is not a per-vertex pass that
could be hoisted).
That work is measured, not assumed. _Within the `cityjson` runner_, on
the same fixture and machine, `full-read` costs roughly a fifth more
elapsed time than `count` in release mode — median of 9 runs, 0.199 s
for `count` against 0.243 s for `full-read` — so the leaf resolution is
real work rather than something the optimiser elides, and
`std::hint::black_box` pins that rather than trusting it to stay true.
(That ~20% is a `cityjson`-internal figure, **not** the
`cityjson`-vs-`cityjsonseq` gap; the cross-format gap also carries the
whole-document-vs-line-oriented parse difference.)
The two rows therefore do the same job, return the same totals (Caveat
42), and differ in where their vertices live: a shared, document-level
array against a feature-local one. A `cityjson`-vs-`cityjsonseq`
`full-read` delta measures that layout and the parse difference, not a
difference in what is visited.
- **Conversion provenance: the chain runs FORWARDS ONLY, and nothing
derives from CityParquet.** Every measured artefact is derived from the
published source document by `benchmark/scripts/readbench_prepare.sh`, in one
direction:
```
CityGML --citygml-tools 2.5.0 to-cityjson--> CityJSON --cjseq 0.3.1 cat--> CityJSONSeq
| |--fcb ser -A---------> FlatCityBuf
| |--cityparquet convert --no-lod0-> CityParquet
|
|--citygml-tools from-cityjson -v 2.0--> CityGML
(only when the source is not itself CityGML)
```
Each artefact derives from the one before it, and **FlatCityBuf and
CityParquet derive from the SAME CityJSONSeq bytes** — that is what makes
their comparison fair. `cityparquet export` could emit the CityJSON
artefacts and it would be convenient, but **deriving a competitor's input
from the format under test would favour that format**, so it is never
done. **CityGML is the one artefact derived backwards**, and the next
caveat is entirely about what that costs. The `cityjson` artefact is the
CityJSON stage without its optional whitespace (Caveat 40).
**One corpus source derives from CityParquet: Montréal.** By the
author's decision, the Montréal CityJSON was exported from this project's
own published CityParquet packages of three boroughs (the derivation is
in `corpus_urls.txt`). The chain above still runs forwards from that
CityJSON, so within a run every artefact, CityParquet included, derives
from the same document; but the document itself passed through
CityParquet first, so whatever the CityParquet encoding could not hold
is absent from every Montréal artefact. A Montréal number compares the
formats on what survived that round trip, not on the city's published
original.
**CityGML synthesis — the one backwards hop, and its cost.** Where the
source document is not itself CityGML, the `citygml` artefact is produced
by `citygml-tools from-cityjson -v 2.0 --no-pretty-print` from the
CityJSON stage. This REVERSES an earlier rule of this benchmark, which
reported such an artefact as "not derivable" and skipped it on the grounds
that a round-trip product is not the source data. Three things about that
reversal, in the order a sceptical reader will raise them:
- **Why it changed.** This benchmark's claim is a comparison BETWEEN
formats, so a dataset that produces four artefacts and skips the fifth
does not weaken the comparison, it removes the baseline from it. Under
the retired corpus the skipped ones were precisely the datasets a reader
recognises — 3DBAG, Rotterdam, Vienna, NYC and Zurich all ship as
CityJSON. Nor is the published `.gml` beside a `.city.json` usually a
way out: of the nine on cityjson.org, six are CityGML **1.0** and two
are **3.0**, and this reader accepts only 2.0 (verified 2026-08-23; the
per-file versions are tabulated in `benchmark/formats/corpus_urls.txt`).
- **What it costs.** The `citygml` row measures **citygml-tools'
serialisation**, not a published file. State this beside any CityGML
number quoted from this corpus. Two things bound the cost. First, size:
on Rotterdam the synthesised document is **14.0 MB** against a published
original of **16.5 MB** — the same magnitude, so a "CityGML is bulky"
finding is not an artefact of the synthesis. Second, formatting:
`--no-pretty-print` is a measurement decision, not a tidiness one. The
same content serialises to **18.8 MB** indented and **14.0 MB** compact,
so indentation alone would move the row by a third. Compact is the
conservative choice — it gives the baseline this benchmark argues
against its **best** case, so no size or parse-time gap can be dismissed
as whitespace.
- **What it cannot preserve, and how the corpus avoids it.** CityGML 2.0
has only integer LoDs. 3DBAG carries LoD 0, 1.2, 1.3 and 2.2, and
`citygml-tools from-cityjson -v 2.0` writes the LoD 1.3 solid as
`lod1Solid` and drops the LoD 1.2 one, so a CityGML artefact
synthesised from it would carry less geometry than the other four. The
3DBAG slice is therefore cut **without LoD 1.2**
(`fcb-slice --drop-lod 1.2`, which `just fetch-3dbag`
passes): all five artefacts hold the same LoD 0, 1.3 and 2.2. The drop
keeps every CityObject and removes the vertices only LoD 1.2 used, so
no format carries orphan coordinates;
`benchmark/readbench/tests/drop_lod.rs` checks that every object then
carries at most one geometry per integer LoD, which is what CityGML 2.0
can hold. A source with two fractional LoDs at one integer level needs
the same treatment before its `citygml` row is content-equivalent to
the others. Two geometries at the SAME LoD on one object are a
different matter, and the whole corpus is normalised against them
before any artefact is built (Caveat 41).
The synthesised artefact is verified after it is written, not trusted:
`readbench_prepare.sh` re-reads it for a 2.0 declaration and for a
`gml:id` on every top-level member (Caveat 15's check, on the other side
of the conversion), and refuses it on either failure.
**Losslessness is asserted, not assumed.** The prepare script counts
top-level objects at each hop — CityGML members by a tag-oriented `awk`
pass, CityJSON via `jq`, CityJSONSeq by counting `CityJSONFeature` lines,
FCB via `fcb info` — and reports any drift across a conversion, because a
CityGML row and a CityParquet row are only comparable where the
conversion between them was lossless. Drift is **reported, not fatal**:
real CityGML routinely carries ADE content citygml-tools skips, and that
is evidence for the write-up rather than a reason to abort. **A run whose
stderr carried a `conversion loss:` warning must have that warning
reproduced beside any number quoted from it.**
No drift check runs across the **synthesis** hop, and running one would be
wrong: CityGML nests a `BuildingPart` inside its parent `Building` where
CityJSON lists both at top level, so the counts legitimately differ — on
a 3DBAG tile, 1,110 top-level GML members against 2,221 CityObjects.
Comparing them would report a 50 % "loss" that did not happen.
- **A known INPUT RESTRICTION: CityGML input whose objects lack `gml:id`
is refused outright.** citygml-tools mints a **fresh random UUID** for any
top-level object with no `gml:id` — a different one on every run. Two
consequences, both fatal to a fair measurement:
- The derived artefacts are **not reproducible**: re-running the chain
produces different ids for the same objects.
- The id the coordinator samples out of the derived CityJSONSeq is
**absent from the `.gml` entirely**, so `citygml`'s `id-lookup` scores a
**miss** (`result_count = 0`) beside every other format's **hit**. That
is a _different query_, not a slower one, and nothing downstream catches
it — the coordinator's cross-format self-consistency check covers
`attr-filter` only, never `id-lookup`.
`benchmark/scripts/readbench_prepare.sh` therefore refuses such a document whenever
`citygml` is in the format set — in its preflight for a CityGML input,
before anything is written, and immediately after writing for a
synthesised one (Caveat 14). Real example, measured 2026-08-16: **Riga's
published `atgazene_lod2.gml` has 703 top-level objects and 703 of them
carry no `gml:id`** (identity lives in a `gen:intAttribute` named
`OBJECTID`).
**No entry of the current corpus trips this check**, and none can trip it
accidentally: every source is CityJSON, where an object's key is what
becomes the `gml:id`. The check is retained and exercised by
`scripts/tests/readbench_prepare_test.sh` precisely because it is not
expected to fire — an unexercised guard is one that stops working at the
next citygml-tools upgrade without anyone noticing.
- **Corpus restrictions — what can be measured is narrower than what can be
converted.** The filters below are recorded per entry in
`benchmark/formats/corpus_urls.txt`, including for the two cityjson.org datasets they
exclude:
- **Single-family datasets only.** The coordinator derives every query
parameter (bbox window, sampled id, attribute predicate) from one
CityParquet package, and refuses a package listing more than one object
table (`locate_cityparquet_table`). A dataset spanning two CityGML
modules therefore cannot be measured at all. Cost, re-verified
2026-08-23 on the current corpus's own source page: **Den Haag tile 01
is excluded for a single `TINRelief` among 844 Buildings and 1,653
BuildingParts** — one terrain object yields a second object table
(`relief.parquet`) and disqualifies the whole dataset — and
**`LoD3_Railway` is excluded for spanning fourteen types across ten
modules**. The Hague's terrain-free tiles are not published separately.
Both would otherwise be good fits, and both would return the moment the
coordinator learns to pick a table from a multi-table package.
- **CityGML 2.0 only.** The reader supports 2.0; citygml-tools reads 1.0
and writes 3.0 by default, so without an explicit version check the
chain would go green around a `.gml` artefact that can never be read.
The prepare script refuses a non-2.0 declaration — in its preflight for
a CityGML input, and after writing for a synthesised one. **This filter
is the reason the corpus is fetched as CityJSON at all**: every CityGML
file cityjson.org publishes beside it is 1.0 or 3.0 (Caveat 14).
- **Unmapped 1st-level types are refused, not counted as zero.** No entry
of the current corpus is affected (all six are Building module), so the
evidence below is from the retired catalogue corpus, where five PLATEAU
modules were excluded on these grounds: `dem`, `trk`, `lsld`, `urf` and
`ubld`. It is kept because the runner behaviour it describes is live.
This
is a _benchmark_ problem, not merely a converter gap: the `citygml`
runner used to report `count = 0`, exit status 0, in a fraction of a
real read's time, while every other format's artefact for the same tile
— produced by citygml-tools, which maps `dem:ReliefFeature` to CityJSON
`TINRelief` — reported thousands. A silent zero beside everyone else's
thousands is the worst possible row, so the runner now **refuses** such
a document outright, naming the offending types. Measured tallies:
`lsld` 134/134 members unmapped, `trk` 11/11, `urf` 2158/2158, `ubld`
1 of 2 (the partial case, which used to report a plausible, plausibly
_wrong_ `count = 1`); `dem` is excluded by inference from the reader's
own type map, never measured (the tile is 599 MB and was never
downloaded), and no member tally is claimed for it.
**Every entry of the current corpus serves every format**, which is the
property it was selected for and what `scripts/tests/fetch_benchmark_test.sh`
asserts about the pinned table. The retired corpus had two entries that did
not — Riga (Caveat 15) and PLATEAU's `brid` tile — and both had to be
fetched by `--only no-citygml`, because either would **abort a default-set
run rather than merely lose a row** (`just bench`'s folder loop runs under
`set -e`). That escape hatch still exists for `$CORPUS_MANIFEST` inputs.
The `brid` tile reads now, and converts to a single-table package: a
`brid:Bridge`'s `lod2Solid` composes its faces by `xlink:href` from
polygons defined in the Bridge's own `brid:boundedBy` semantic surfaces,
and the generic reader harvests those as xlink targets
(`citygml_generic_object_boundedby.rs`). Nothing in this caveat rules it
out any more; whether to restore it to the corpus is a corpus-selection
question, and it is not measured until it is.
- **The `attr-stats` scenario is not uniform across the corpus.**
`nyc_da13_buildings` (23,777 objects) carries **no numeric attribute at
all**, so it has no `attr-stats` row. Elsewhere the aggregated column
covers all of a dataset's CityObjects or only part of them:
| dataset | column | CityObjects carrying it |
| ---------------------- | -------------------- | ----------------------- |
| `rotterdam_delfshaven` | `TerrainHeight` | 853 of 853 |
| `vienna_102081` | `measuredHeight` | 1,102 of 1,322 |
| `zurich_building_lod2` | `GebaeudeStatus` | 52,834 of 198,699 |
| `tokyo` | `measuredHeight` | 38,743 of 49,915 |
| `montreal` | `measuredHeight` | 31,415 of 31,415 |
| `3dbag_n1000000` | `b3_bag_bag_overlap` | 331,363 of 1,000,001 |
Tokyo's column is on its `Building`s, not its 11,172
`BuildingInstallation`s, and 1,041 of its 38,743 values are PLATEAU's
`-9999` "not measured" placeholder. They are aggregated with the rest:
no runner has an exclusion predicate, and adding one to every runner
would change the timed work of every format. Tokyo's `min`, `sum` and
hence its mean are therefore not heights, and its `attr-stats` row is a
timing, not a statistic of the city.
`just bench` takes the column from the dataset's hand-picked entry
(`HAND_PICKED_STATS` in `benchmark/readbench/src/params.rs`: Tokyo and
Montréal) or derives it, and omits the row where there is none, so an
absent `attr-stats` row is expected rather than a failed measurement.
**Do not read a missing row as a zero, and do not average a
partial-coverage row into a cross-dataset aggregate figure.** Every other
scenario is unaffected: they derive from geometry, object type or id,
which every dataset has.
The retired corpus had a different version of this problem — two datasets
with 1 and 13 top-level objects, whose every filter matched either
everything or nothing — and the current corpus has none: its smallest
entry holds 379 objects, so every selectivity ratio carries information.
- **Wire size equals disk size on this corpus, but the SOURCE size is not
the size of what gets measured.** Every current entry is served
uncompressed, so the pinned wire bytes are also the bytes on disk — unlike
the retired corpus, where `.zip`/`.gz` entries differed by up to 30x
(Estonia's 11 MB archive expanded to 323 MB) and the disk figure was the
one to quote. That distinction no longer applies here.
What does apply: **a dataset's pinned size is its CityJSON size, and four
of the five measured artefacts are not that file.** The synthesised
CityGML is several times larger (Rotterdam: 2.7 MB CityJSON, 14.0 MB
CityGML), the CityParquet package is smaller, and so on. When relating "a
dataset's size" to a read time or a peak-RSS number, use the per-format
bytes in `sizes.csv`, never the corpus table's wire figure — that one
answers download cost for the source only.
- **FlatCityBuf's `id` is structurally unindexable, so its `id-lookup` is a
full walk.** The `.fcb` artefacts are built `fcb ser -A`, which indexes
**every attribute**, and the runner does try the B+-tree first. It always
falls back: `id` is a CityObject's map KEY, never a member of the
CityJSON `attributes` map FCB's schema covers, so there is no entry to
find. The `no-attr-index` tag on those rows records a property of the
format as it stands, not a gap in this harness. `attr-filter` uses the
index whenever its predicate column IS a member of that `attributes`
map, and falls back to the same full walk — carrying the same
`no-attr-index` tag — whenever it is not, which is the case for the
reserved `object_type`. The tag in the CSV, not the scenario name, says
which of the two a given row measured.
- **A probe's POSITION is only nominal outside the CityJSONSeq stream.**
The deciles are cut from the seq order, which is the source CityJSON
document's own object order, so the CityJSON artefact shares it. The
FlatCityBuf and CityParquet artefacts are cut from that same file — but none of the
CityGML, FlatCityBuf and CityParquet artefacts preserves it. The CityGML
is synthesised independently by `citygml-tools`; the `.fcb` is ordered by
its own R-tree; the CityParquet package's rows are in Hilbert-curve
order, so a probe sits wherever its object's Hilbert key puts it. The
CityGML and FlatCityBuf rows show **non-monotonic** times across
`id-10pct`/`id-50pct`/`id-90pct`. Presence is
verified for every probe; position is not. Read the three hit rows for
any of those three formats as three samples of the distribution, never
as a position curve.
- **A bbox row's target and its achieved selectivity can differ, and
`approx` says where.** Row counts are discrete, so a target is not always
reachable: 1% of `vienna_102081`'s 1,322 rows is 13.22 rows. The search accepts
a relative tolerance of ±10% and, when it cannot converge inside that,
takes the nearest achievable window and appends `approx` to the row's
`notes`. A missed target is disclosed in the artefact, never silent.
No window edge lies on an object's edge. The search converges on a jump
in the row count, so its edges land exactly on object edges, where
whether an object counts depends on the last digit of how a format
decodes the coordinate — a CityJSON integer times its scale against the
Parquet double. Each `x`/`y` edge is therefore moved to the midpoint of
the gap between two consecutive distinct object-edge values (a lower
edge competes with the boxes' maxima, an upper edge with their minima);
the gap must exceed twice the dataset's coordinate quantisation (the
CityJSONSeq `transform.scale`) on that axis, and when the edge's own gap
is narrower the nearest wide one is used. `z` spans the dataset's range
with a margin either side. `achieved` and `approx` describe the moved
window.
- **bbox targets are expressed in CityParquet ROW space.** The search runs
over the CityParquet package's per-row bboxes, so `cityparquet`'s own
achieved selectivity is the one that lands on the target. Feature-grained
formats (`citygml`, `cityjsonseq`, `flatcitybuf`) count a different unit
and therefore report a different achieved fraction for the **identical**
window — on `vienna_102081`, 0.026 against CityParquet's 0.0098 for
`bbox-1pct`. The window is the same for every format, which is what makes
the timings comparable; the `selectivity` column is not comparable across
grains. See Caveat 1 on counting grain.
The window is defined in the package's axis order: a CityParquet package
stores `x` as longitude and `y` as latitude, as GeoParquet requires,
whatever order the CRS declares. Every other artefact keeps the source's
order, so for a CRS that declares latitude first (Tokyo's JGD2011,
EPSG:6697) `citygml`, `cityjson`, `cityjsonseq` and `flatcitybuf` receive
the window with `x` and `y` swapped. The parameter sidecar records the
fact as `swap_xy`, read from the CRS's declared axis directions, never
guessed from coordinate magnitudes; the `cityparquet` runner reads the
package and uses the window as written.
- **The format set is uniform across every dataset, the 3DBAG slice
included.** Every dataset the suite measures gets all five formats,
so a `citygml` row exists wherever any other row does and every
factor against CityGML has its baseline. On the slice, as on every
corpus dataset, that CityGML is a `citygml-tools` serialisation the
preparation chain synthesises, not a published file (Caveat 14), and it
is the largest artefact the chain writes, larger than the CityJSONSeq
it derives from by the factor the run's `sizes.csv` records.
Synthesising it is the longest step of `just
bench-prep`, and parsing it dominates the slice's `citygml` rows; quote
those rows as the cost of reading this serialisation of the slice, with
the same qualification as every other `citygml` number.
- **The `bloom` family measures the 3DBAG slice alone, because pruning
needs several row groups.** A filter rules out whole row groups. At the
default 65 536-row groups a table of fewer rows is one row group, on
which a hit can skip nothing and which says nothing
about how pruning scales. The 3DBAG slice's 1,000,001 rows span 16 row groups and
Zurich's 198,699 span 4; every other corpus dataset holds fewer than
65 536 CityObjects and is a single row group. The family therefore runs
on the slice only. Its probes are three lookups, each as a hit and a
miss: `id-lookup`, `feature-lookup` and `attr-lookup`, an equality
lookup on each configured text attribute (`bloom_attributes`; a run
without attribute rows is shown in the summary as not measured). A hit still costs the
row groups that hold the value, because a filter cannot narrow the
search inside a group: on a 3DBAG tile written with 9 row groups,
`documentnummer`'s hit returned 23 rows and its filters pruned 6 of the
9 groups, so the reader still scanned 3. A miss can still read a group
through a false positive at the 1 % target rate. The benefit depends on
the row-group size, which the benchmark does not vary (65 536 rows per
group, the writer's default). `cityparquet+nobloom` still carries
row-group statistics, so the axis measures what the filters add on top
of statistics, and every lookup row records both pruning counts.
Row groups are pruned in two steps: the Bloom filter first, then the
column chunk's min/max statistics. `bloom_pruned` counts the row groups
the filters rejected and `stats_pruned` those the statistics rejected
among the rest, so the two never count one row group twice. Each miss
probe is chosen INSIDE the row groups' stored value ranges (a stored
identifier or attribute value with `-readbench-absent` appended), so
min/max statistics alone cannot reject it and a miss row measures the
filters, not the statistics.
- **In the `bloom` family, a filter's positive is not a match.** At FPP 0.01
a miss can still keep a row group; `row_groups_total − bloom_pruned −
stats_pruned` on the `*-miss` rows is how many the reader still
scanned.
- **The `bloom` family reads the footer twice per lookup** — once for the
decode metadata, once inside the lookup — equally for both variants.
- **The `bloom` family measures single-table packages only.** The runner
queries one object table; a multi-table corpus package is refused, never
partially read.
- **In the `bloom` family, `feature-50pct` is the `id-50pct` feature**, and
`feature-miss` the same verified-absent string as `id-miss` (absent from
both columns).
- **The `bloom` family's requests are logical.** Over `--transport http`,
`CountingObjectStore` counts the requests the reader made after
object_store coalesced nearby ranges — not raw wire traffic, retries or
connection reuse.
- **The format evidence is measured on bloom-enabled packages.** The
`formats`, `sizes` and `bloom` families read packages the bloom-enabled
writer produces, and each results directory carries a `MACHINE.md`
describing the host it was measured on. The writer puts filters on
`id`, `feature_id` and high-cardinality string attributes by default, so
its packages are larger and its lookups prune; the `bloom` family's
`cityparquet+nobloom` variant is the one package measured without them.
The coordinator writes 22 columns, with `stats_pruned` after
`bloom_pruned`, and applies the return rule of "The six scenarios".
- **One generation of results, one timing block, one header.** Every
results CSV reports the seven-column timing block
(`time_mean_s` .. `time_q3_s`) over the warm samples, in the
coordinator's 22-column shape (Caveat 30). Results with a median and
`time_mad_s`, results measured on packages without default-on bloom
filters, and results in the 21-column shape that predates `stats_pruned`
exist only in git history and must not be set beside these: a median
absolute deviation and an interquartile range are different spreads, and
an `id-lookup` without filters is a different operation. The
`time_mad_s` shape is kept apart mechanically: the coordinator's
`CSV_HEADER` (`benchmark/readbench/src/coordinator.rs`) is the only
writer of a results header, `benchmark/plot/tests/test_csv_contract.py`
holds the renderer's `READ_COLUMNS` to a leading prefix of it, and the
summary loader refuses to render a CSV whose leading columns differ
from `READ_COLUMNS` (a `time_mad_s` header among them). The 21-column
shape shares that prefix and loads, so it and the unfiltered packages
are kept apart by provenance (`MACHINE.md`, the run's commit), not by
the header.
- **FlatCityBuf is read through the raw FlatBuffers accessors, not
`cur_cj_feature`.** Every FCB walk — `full-read`, the `attr-filter`
fallback, `id-lookup`, `attr-stats` — reads
`FeatureIter::cur_feature()`, the zero-copy `CityFeature` table, and
touches only what the scenario's answer needs: one flatbuffer enum for
`object_type`, one borrowed `&str` for an id, one targeted decode of a
single column out of the packed attribute blob, and — for `full-read`
alone — every geometry's flattened `solids`/`shells`/`surfaces`/
`strings`/`boundaries` arrays, its per-surface `semantics` indices,
every template instance's boundaries and every vertex. Not read by
`full-read`: `semantics_objects`, `material`, `texture` and the feature
`appearance`.
These rows used to call `cur_cj_feature()`, which runs `fcb_core`'s
`to_cj_feature`: a whole CityJSON feature — nested `serde_json` boundary
arrays, every vertex converted, every attribute into a
`serde_json::Map`, a `String` id per CityObject — built to answer a
question needing one comparison. That made FCB's attribute/id rows
cost the same as its `full-read` row, and measured the conversion rather
than the format. Measured on this machine, 3 repeats, medians, identical
`result_count` in every pair: on a 10,000-object prefix of the 3DBAG
stream `full-read` 0.404 s ->
0.037 s, `attr-filter` on `object_type` 0.405 s -> 0.034 s, `id-miss`
0.407 s -> 0.035 s, `attr-stats` 0.404 s -> 0.045 s; on
`zurich_building_lod2` the same four rows 2.22/2.05/2.22/2.22 s ->
0.55/0.50/0.50/0.51 s. Peak heap falls with
them (1.3 MB -> 0.3 MB on zurich). The INDEXED paths
(`select_attr_query`, `select_query`) were already native and are
unchanged — `b3_dak_type == slanted` on that prefix sits at ~1 ms
either way.
FlatCityBuf is a comparison baseline here, not this repository's
subject, so it is owed its best natural implementation. Published
FlatCityBuf figures produced before this change overstate its
attribute-, id- and full-read costs and must not be mixed with figures
produced after it.
- **One FlatCityBuf configuration, chosen by measurement.** The `.fcb`
artefacts are written `fcb ser -A` with every other index knob left at
its default — attribute B+-tree branching factor 256, R-tree node size 16. That is a decision, not an oversight, and it is not a swept axis:
the paper's only configuration axis is CityParquet's own (bloom
filters).
A 100,000-object prefix of the 3DBAG stream was written at attribute
branching factors 16, 64, 128 and 256 and read back with the benchmark's own child (3 repeats,
medians): `count` 0.1 ms, `bbox-1pct` 0.3 ms, `bbox-5pct` 1.0 ms,
`bbox-25pct` 3.4-4.0 ms, the indexed `attr-filter` (`b3_dak_type ==
slanted`) 5.2-5.4 ms and the `id-lookup` miss 0.31-0.32 s — every
scenario within noise of every other factor, with identical
`result_count`s and file sizes inside 0.5%. `zurich_building_lod2`
agrees at 16/128/256, including on an indexed `attr-filter` that really
matches (`GebaeudeStatus` in [1, 1], 52 834 rows): 0.605/0.605/0.607 s
over 7 interleaved repeats. No factor wins, so the default stands.
`--index-node-size` must be left alone for a different reason: it is
**unreadable**. `fcb_core` 0.7.6's `FcbReader::select_query` — the
seekable path this benchmark reads through — passes
`PackedRTree::DEFAULT_NODE_SIZE` where its streaming sibling
`select_query_seq` passes the header's own `index_node_size`, so a file
written `fcb ser -A --index-node-size 64` panics with a capacity
overflow on every `bbox-query`. Reproduced here, not inferred.
- **`peak_rss_bytes` is the child's own `VmHWM` on Linux, because
`ru_maxrss` there is floored at the coordinator's RSS.** A child that
reported `getrusage(RUSAGE_SELF).ru_maxrss` could never report less
than the coordinator's peak: Linux's `exec_mmap` folds the high-water
mark of the address space a task had BEFORE `exec` into
`signal->maxrss`, and under `posix_spawn`/`vfork` that address space is
the parent's. Every format that used less than the coordinator would
then carry the coordinator's memory, not its own, and any read-memory
ratio computed from such values would be a ratio of floors.
The child therefore reads `VmHWM` from `/proc/self/status` (its own
`mm`, created by `exec`, is not inherited; measured 10.5 MB for a child
under a 420 MB parent, against 419 MB from `ru_maxrss`). Reading it
changes no timing. **Read-memory figures taken with `ru_maxrss` on Linux
must not be set beside these**, and a value equal to the coordinator's
own RSS is not a format's memory.
- **Before 2026-09-23, only CityParquet's `attr-stats` computed the
aggregate.** The scenario's contract is `(min, max, sum, count)` of a
numeric attribute, but the `citygml`, `cityjson`, `cityjsonseq` and
`flatcitybuf` runners only COUNTED the CityObjects carrying a numeric
value: the same `result_count`, a cheaper question. Every runner now
folds each value into all four aggregates — `as_f64()` of the JSON (or
FCB-decoded) value, integers and floats alike, `f64` `min`/`max`/`+=` —
and pins them with `std::hint::black_box`, so the optimiser cannot reduce
the arithmetic to the count. CityParquet is unchanged: min and max from
column-chunk statistics, sum and count from a one-column projected scan,
which is the format's own mechanism. The child reports the four values on
stderr (`cityparquet-readbench: attr-stats <min> <max> <sum> <count>`,
after its timed line; the CSV is unchanged), and
`tests/attr_consistency.rs` holds every format to DuckDB's
`min/max/sum/count` on `delft.city.jsonl` over two integer and two float
columns — they agree exactly, not merely within the test's 1e-6
(CityParquet's statistics-derived minimum of `b3_bag_bag_overlap` prints
as `-0` where the folding runners print `0`: Parquet writes a zero
minimum as `-0.0`, and the two are equal under IEEE comparison). The
`result_count`s did not change. **`attr-stats` timings of the
non-CityParquet formats from before and after this change must not be
mixed**; the added work is one comparison pair and one addition per
value, next to a full parse, so the difference is expected to be small
but it has not been measured.
- **FlatCityBuf's schema has no CityObject `address`.** Tokyo carries an
`address` on 35,067 CityObjects; `fcb ser` writes none of them, so the
`.fcb` holds less than the other four artefacts. No query reads an
address, so no `result_count` is affected, but a Tokyo size or full-read
figure compares a FlatCityBuf file without addresses against files with
them.
- **The package is written without LoD 0 synthesis (`--no-lod0`), so it
holds the same geometries as every other artefact.** By default
`cityparquet convert` synthesises an LoD 0 footprint for every object
without a source LoD 0, which no other format's artefact holds.
`readbench_prepare.sh` and the coordinator's variant packages turn it
off; the library default is unchanged. A source LoD 0 is kept: Tokyo's
38,743 `Building`s keep theirs, and its 11,172 `BuildingInstallation`s
(LoD 2 or 3 in the source) gain none. With synthesis on, the package
gained a footprint for 788 of those installations and for the 12
Montréal buildings without LoD 0, and on Rotterdam, Vienna, New York and
Zurich, which are LoD 2 only, for every object. Synthesis changes no
`result_count` (verified on Tokyo, with and without it), but it adds
bytes: on Tokyo the package is 80,249,026 B with it and 80,184,081 B
without, 64,945 B or 0.08 %; on Rotterdam it is 780,544 B with it and
714,076 B without, 66,468 B or 8.5 %, a footprint for each of its 853
buildings.
- **On Tokyo the two counting levels are far apart: 49,915 CityObjects in
38,743 features.** Each feature is a `Building` with its
`BuildingInstallation`s inline, so the feature-grained formats
(`citygml`, `cityjsonseq`, `flatcitybuf`) count 38,743 for `count` and
the `full-read` `result_count`, while every format returns CityObjects
for each `bbox-*` window; Caveat 1 states which is which, and the
coordinator checks each level against its own reference (Caveat 2).
Vienna (1,322 in 307) and Zurich (198,699 in 52,834) are nested too; Rotterdam, New York and Montréal hold one
CityObject per feature.
- **Isolation on a shared host is best effort, and recorded rather than
assumed.** The citable host is shared and the harness has no root, so it
pins the measured children to one NUMA node (`numactl`, else `taskset`
without memory binding), keeps the coordinator on that node's first core,
can cap the children's memory through `systemd-run --user`, and holds each
sample while the node's share of the load exceeds `--max-load`. It cannot
change the CPU governor, disable SMT or turbo, drop page caches or isolate
cores (`isolcpus`). What a run requested and what was applied is in its
`.params.json` under `isolation`, with each sample's `load1`, runnable
count and `MemAvailable` in `.samples.json`; a params sidecar without an
`isolation` object comes from a run that applied none of it. A row tagged
`busy` ran after the longest wait with the node still contended and is not
cited without its spread. The samples of one cell run back to back, with
no interleaving across formats, so slow drift in co-tenant load lands on
whole cells rather than averaging out across formats.
- **The `cityjson` artefact is measured without optional whitespace, like
the `citygml` artefact (`--no-pretty-print`, Caveat 14).**
`benchmark/scripts/compact_json.py` writes it: it removes the whitespace
outside strings and copies every other byte, so string contents and
number spellings are the published ones and an already-compact document
is unchanged. A JSON parser and serialiser would not do: `jq -c` (1.7.1)
respells numbers (Tokyo's `1e-10` scale becomes `1E-10`, which shrinks
Tokyo by 986 B), though every value survives. All six corpus sources
are compact as published (montreal 498,371,022 B, nyc_da13_buildings 110,083,137 B,
rotterdam_delfshaven 2,731,804 B, tokyo 315,968,009 B, vienna_102081
5,635,634 B, zurich_building_lod2 292,500,409 B), and so is the
normalised source Caveat 41 derives from one of them, so on the current
corpus the step is a no-op: each `cityjson` artefact is byte identical
to the source the chain builds from — the published one for five
datasets, and for vienna_102081 (4,731,370 B) the normalised one. The
normalised source is written by `serde_json`, which writes the shortest
round-trip spelling of each number: every value survives, and
vienna_102081's spellings are the published ones, but a source that
spells a float otherwise (`-1.1e-06` for `-1.1e-6`) would be respelled,
so the published spellings are guaranteed for the five pass-through
datasets only. A size or
parse-time gap against CityJSON therefore cannot be dismissed as
whitespace.
- **The corpus holds at most one geometry per LoD and object.** Before any
artefact is built, `readbench_prepare.sh` normalises a CityJSON or
CityJSONSeq source with `lod-normalise`
(`benchmark/readbench/src/lod.rs`, `keep_first_per_lod`): each
CityObject keeps the first geometry at each LoD in source order, the
vertices nothing references any more are removed and the boundaries
re-indexed, and every CityObject is kept, with or without geometry. The
kept geometries' semantics, `material` and `texture` are unchanged;
texture coordinates (`vertices-texture`) and the `appearance` arrays are
left as they are, so every kept reference still resolves and a dropped
geometry's texture coordinates stay in the shared array. A changed
source is re-serialised compactly; an unchanged one is used byte for
byte. This is a property of the CORPUS, chosen because CityParquet
stores one geometry column per LoD: its writer keeps the first geometry
at a LoD and drops the rest (`skipped_same_lod_geometries`), so without
the normalisation the package would hold fewer geometries than the other
four formats. "The same LoD" is the writer's key exactly — the `lod`
string as `Lod::parse` reads it, so `2` and `2.0` are one LoD — and a
GeometryInstance never claims a LoD. The CityParquet stage then asserts
that the writer skipped nothing, and fails the dataset otherwise. On the
corpus, vienna_102081 loses 1,102 of its 2,204 geometries (each LoD 2
object carries a MultiSurface, kept, and a Solid, dropped; no vertex is
orphaned); the other five sources and the 3DBAG slice are unchanged. A
CityGML source is not normalised; none is in the corpus.
- **Cross-format consistency covers what is returned, not only how
many.** After the counts of Caveat 2, the coordinator's
`check_returned` compares, per scenario and query tag, what every
format returned: the identifier-set digest (count, sum and xor of the
identifiers' hashes) of each `bbox-*` window and of `attr-filter`; the
number of geometries returned by each window and the extent their
visited coordinates span; and, for `full-read` and each `id-*` probe,
the CityObjects, geometries and semantic faces visited and their
extent. Extents are brought into the package's axis order and must
agree within one quantisation step of the package's `transform` per
axis; every other value must be equal. A disagreement tags the rows
`count-mismatch` and fails the run. A row that reports no value for a
part is named on stderr rather than passed silently.
A format may be excused from one comparison only through the explicit
`TOTAL_EXCLUSIONS` list in the coordinator, each entry naming the
format, the comparison and the reason, and pinned by a test. The list
holds one entry: **`citygml` is not compared on the `full-read` and
identifier-lookup `extent`**. CityGML stores real coordinates and no
`transform`, so the library's CityGML reader quantises them with its
own step, derived from the CRS's units — a millimetre on a metre axis.
Where the source's `transform` is finer (Tokyo's height step is
1e-6 m), the CityGML extent lies up to half a millimetre from the other
four formats', beyond the one-step tolerance. Its spatial windows'
returned extent, and every count, digest and total, are still compared.
- **The corpus holds only content that CityGML 2.0, the baseline, can
express.** Every artefact derives from one source, and the comparison
is fair only if all five formats hold the same content, so a dataset
whose CityGML 2.0 synthesis loses content is excluded. Ingolstadt is
excluded on this rule. Its 32,670 Window and Door semantic surfaces
name no parent surface, and CityGML 2.0 can hold an opening only inside
a wall or roof surface; `citygml-tools from-cityjson -v 2.0` writes the
references but omits the polygons, and prints no warning, so its
synthesised CityGML holds about a quarter of the dataset's faces. No
corpus dataset carries Window or Door semantics, and none has a
dangling reference; as a consequence the corpus has no LoD 3 dataset
with openings, and its only LoD 3 content is Tokyo's 21 LoD 3 solids
and 103 LoD 3 installation geometries.
- **The network family's times come from a simulated network.** The
primary over-HTTP measurement reads through `net-sim`, a local server
with a fixed bandwidth (one budget shared by every connection) and a
fixed latency per request, with no TLS, HTTP/2, connection set-up,
jitter, loss or service-side throttling. Its times are therefore a
model of the transfer: a reader should cite the bytes read and the
request count as the format's properties, and the time as what one
stated profile makes of them. A run against real object storage is a
snapshot of one path at one time. How each client issues its requests
decides what the latency multiplies: the CityGML, CityJSON and
CityJSONSeq arms fetch the whole object with one `GET` per query, so
every query costs the whole file's transfer plus one latency, with one
exception: the identifier lookup of the two streaming formats, CityGML
and CityJSONSeq, parses the body as it arrives and abandons the
transfer at the hit, so its `bytes_read` is the bytes received up to
the hit (chunk-granular, counted on the body stream, not by
`CountingObjectStore`), and only a miss reads the whole file. CityJSON
is one JSON document and cannot stop early. On an abandoned transfer
the simulated server has sent more than the client received (what was
in flight); the params sidecar reports that gap as
`server.unreceived_bytes` rather than folding it into either total. The
CityParquet arm reads `metadata.json`, then each Parquet file through
`parquet`'s `ParquetObjectReader` over `object_store` (a `HEAD` for the
size, the footer, then the column chunks of the row groups that
survive pruning, as ranged `GET`s that `object_store` may coalesce and
issue concurrently); the FlatCityBuf arm reads through
`http-range-client`'s buffered client, one ranged `GET` at a time, for
the header, the spatial or attribute index and then the features.
Because the text formats' bytes and requests are the same for every
query, and their time nearly so (within 0.92–1.15× of read all in the
end-to-end runs on Rotterdam and Vienna, with the transfer 83–94 % of
it at `typical`), they are measured by default only on read all and the
four identifier lookups; their other six queries are derived rows.
A derived row is written only when every sample of the format's read
all and identifier miss made one request and read exactly the
artefact's size (an identifier hit cannot prove it: the streaming
formats stop at the hit),
copies read all's bytes and request count, and has no time, memory or
result count (`notes` carries `derived-from=full-read;status=derived`).
It states that the query costs the same transfer as read all; it is not
a measured time, the figure marks it with `†`, and the cross-format
consistency check skips it. `[network] whole_file_scenarios` in
`manifest.toml` (`--network-whole-file-scenarios`) selects
`full-read,id-lookup` (the default), `full-read` or `all`, the last
measuring every query.
- **FlatCityBuf's indexed attribute filter over HTTP reads many times its
file, and the cause is the pinned library, not the format.** On
Rotterdam (a 2.95 MB `.fcb`) the `attr-filter` query
(`TerrainHeight >= 2.45`, 217 matching CityObjects) issues 144 ranged
`GET`s and reads 113.5 MB, about 38 times the file, while `full-read`
takes 4 requests and 2.95 MB and the 1 % window 4 requests and 85 kB.
The runner opens the file once and reads each hit once; the bytes come
from `fcb_core` 0.7.6 and `http-range-client` 0.9.1. The B+-tree node
fetch (`static_btree/stree.rs`) sets the buffered client's minimum
request size to 1 MiB and nothing resets it, and the hit list comes
back in key order, not file order, so a hit that falls before the
buffer, or past its end, clears the buffer and fetches 1 MiB (or up
to the end of the file) from that feature's offset; the next hit
usually falls outside that buffer again. The spatial window does not
show this because its iterator sets its own request size and visits
the features in file order. Neither text equality filter on this
corpus reaches this path. Vienna's `roofType = FLACHDACH` names 600
CityObjects in a 307-feature file, so its hit list is longer than the
feature count and the row falls back to a `select_all` walk (tagged
`attr-index-failed`; 9 requests, 4.6 MB for a 3.6 MB file). Tokyo's
`usage = "401"` gets no hits from the HTTP index at all and takes the
same tagged walk (next item). The
attribute filter's bytes, requests and time over HTTP are therefore a
property of this client, and a reader should not cite them as the
cost of FlatCityBuf's attribute index; the local arm reads a file
handle and is not affected.
- **FlatCityBuf's HTTP attribute index drops every key that names 4,096 or more features**, and the cause is the pinned library, not the format.
On Tokyo (a 265 MB `.fcb`), `usage = "401"` names 12,348 CityObjects
through the local index, but the same query through `fcb_core` 0.7.6's
`HttpFcbReader` returns an empty hit list after 4 requests and 1.09 MB.
The same happens to `class = "3001"` (11,599 locally) and to
`延べ面積換算係数 = "1"` (19,956), while keys with 566 and 84 matches
come back whole. A key's B+-tree payload entry is 4 bytes plus 8 per
matching feature. The HTTP reader prefetches 16 KiB of the payload
section for a column with few distinct values
(`compute_payload_prefetch_size`). It re-fetches an entry that does not
fit in that prefetch with a fixed 32 KiB request
(`batch_resolve_payloads`, `static_btree/stree.rs`). An entry longer
than 32 KiB, which is any key naming 4,096 or more features, then fails
to decode, and the reader drops it without an error. The local reader
reads the whole entry from the file handle and is not affected. The HTTP
arm therefore never publishes an empty index answer: it verifies it by
the full `select_all` walk, tagged `attr-index-failed`. On Tokyo that
walk returns the 12,348 objects, with the same identifier digest as the
local index, for 254 requests and 261.4 MB. A genuine miss costs the
same walk. A range predicate is not guarded: one heavy key inside the
range would be dropped from a non-empty answer, which only the
cross-format consistency check detects.