getwkb allocates far more than its output size for any geometry whose subgeometries carry their own header (MultiPolygon, MultiPoint, GeometryCollection), growing linearly with the subgeometry count at constant output size.
import GeoInterface as GI, GeoFormatTypes as GFT, WellKnownGeometry as WKG
ring(n) = GI.LinearRing([(Float64(i), Float64(i)) for i in 1:n])
mpoly(k) = GI.MultiPolygon([GI.Polygon([ring(100_000 ÷ k)]) for _ in 1:k]) # 100k vertices total
WKG.getwkb(mpoly(2)) # warm up
for k in (1, 25, 100, 400)
mp = mpoly(k)
nb = length(GFT.val(WKG.getwkb(mp)))
a = @allocated WKG.getwkb(mp)
println("k=$k\tout=$nb B\tallocated=$a B\t$(round(a/nb, digits=1))x")
end
| geometry |
subgeoms |
output |
allocated |
amplification |
| MultiPolygon |
1 |
1.60 MB |
6.25 MB |
3.9x |
| MultiPolygon |
25 |
1.60 MB |
52.4 MB |
32.8x |
| MultiPolygon |
100 |
1.60 MB |
216.8 MB |
135.4x |
| MultiPolygon |
400 |
1.61 MB |
877.4 MB |
546.6x |
| GeometryCollection |
400 |
1.61 MB |
949.6 MB |
591.6x |
| MultiPoint |
100k |
2.10 MB |
297.1 GB |
141461x |
| Polygon (control) |
1 → 400 rings |
1.60 MB |
6.25 MB |
3.9x (flat) |
Polygon stays flat because PolygonTrait goes through repeat=false, while the multi-* traits are <: GI.AbstractGeometryCollectionTrait and so take _getwkb!(..., repeat=true), which passes first=true for every subgeometry — and the if first branch calls sizehint!(data, 42) (src/wkb.jl:113; sizehint!(data, 21) at src/wkb.jl:68 for points); since sizehint! below the current length shrinks capacity down to length, every subgeometry discards the amortized doubling growth and the next push! reallocates the whole buffer. Emitting the 5-byte subgeometry header on that path is correct per the WKB spec — only the sizehint! is accidental.
Guarding both call sites with isempty(data) && flattens every row to 3.9x (MultiPolygon k=400: 877.4 MB → 6.25 MB; MultiPoint: 297.1 GB → 6.25 MB) with byte-identical output (sha256-verified on all three shapes).
WellKnownGeometry v0.2.6, GeoInterface v1.6.1, Julia 1.12.6.
getwkballocates far more than its output size for any geometry whose subgeometries carry their own header (MultiPolygon, MultiPoint, GeometryCollection), growing linearly with the subgeometry count at constant output size.Polygon stays flat because
PolygonTraitgoes throughrepeat=false, while the multi-* traits are<: GI.AbstractGeometryCollectionTraitand so take_getwkb!(..., repeat=true), which passesfirst=truefor every subgeometry — and theif firstbranch callssizehint!(data, 42)(src/wkb.jl:113;sizehint!(data, 21)atsrc/wkb.jl:68for points); sincesizehint!below the current length shrinks capacity down tolength, every subgeometry discards the amortized doubling growth and the nextpush!reallocates the whole buffer. Emitting the 5-byte subgeometry header on that path is correct per the WKB spec — only thesizehint!is accidental.Guarding both call sites with
isempty(data) &&flattens every row to 3.9x (MultiPolygon k=400: 877.4 MB → 6.25 MB; MultiPoint: 297.1 GB → 6.25 MB) with byte-identical output (sha256-verified on all three shapes).WellKnownGeometry v0.2.6, GeoInterface v1.6.1, Julia 1.12.6.