mapbox / rio-rgbify

Encoded arbitrary bit depth rasters in pseudo base-256
MIT License
102 stars 38 forks source link

Add round-digits option to have better image compression #34

Closed frodrigo closed 2 years ago

frodrigo commented 2 years ago

In short

Add an option to arbitrary round the encoded elevation value. Switch to 0 an arbitrary number of lower digit to reduce the noise into the RGB encoded image. Less noise achieves a better compression. It has the counter part to approximate the encoded elevation.

For low zoom level (like 8) the elevation change between pixel is hight, but the elevation precision required at this overview level is low. We can erase lot of lower digits and have high compression gain.

At upper zoom (like 12), the noise on lower digits is lesser. But we can still save space by round some few lower bits.

Long version

An original tile of z8. 430 KB.

rio-rgbify-04-z8-92

If we look at the different colors separately of a level 8 tile, we can clearly see that red does not vary, green varies little and blue a lot.

Decomposition of the levels of Red, Green and Blue. Note that the first one is indeed "red", but very dark, uniform value: 1 out of 255.

rio-rgbify-08-ChannelRGB

It can be seen that the Blue channel is much noisier than the others, especially for low zoom levels where the altitude varies a lot from one pixel to its neighbors, almost at random. If we delete the blue channel (we can't really delete it, but set the value to 0 for all pixels) then the same image compressed in PNG is only 91 Kb, or 80% less. Tthis very noisy data that makes the image difficult to compress. The more random the values are, the greater the entropy, and therefore the ineffective compression.

Tile at zoom level 8 containing 0 for the blue value. 97 Kb, or 77% less.

rio-rgbify-09-z8-92RG

Tile with zoom level 12 containing 0 for the blue value. 41 KB, or 87% less.

rio-rgbify-10-z12-1484RG

The blue channel has an amplitude of 256 levels, one level represents 10 cm. We have therefore introduced a maximum error of 256 * 0.1 = 25.6 m in altitude.

For example, at a zoom level of 8, the horizontal difference between two pixels is 431 m, an altimetry error of 25 m may be acceptable.

Animation showing the shading at zoom 8 before and after changing the values stored in the blue to 0. Having trouble seeing the animation? That's a good sign!

rio-rgbify-11-diff-optim

The same area with an extract enlarged x4 to see better what's going on.

rio-rgbify-12-diff-optim_x4

Differences in shading values before and after modification.

rio-rgbify-13-diff

Removing the blue-encoded part is in fact equivalent to rounding the altitude to the nearest 25 m and setting the last 8 bits (the bits encoded in blue) to 0. This can be extended by varying the level of precision on which the altitude is rounded and therefore the number of last bits that are set to 0 depending on the zoom level.

The processing of the French DEM at horizon precision of 25m to z5 up to z12 produce an 1.3 GB in PNG instead of 3.7, which is a 65% reduction in size, and 0.8 GB in WebP instead of 2.2 GB, which is also a 65% reduction in size. Using a ramp of rounding precision from 11 digits à z5 down to 4 digits at z12.

frodrigo commented 2 years ago

Read the full story at https://medium.com/@frederic.rodrigo/optimization-of-rgb-dem-tiles-for-dynamic-hill-shading-with-mapbox-gl-or-maplibre-gl-55bef8eb3d86

dnomadb commented 2 years ago

Thanks again @frodrigo ! I'll get this merged and pushed to pypi first thing tomorrow.

systemed commented 2 years ago

@dnomadb quick friendly reminder to merge @frodrigo's great PR :)

systemed commented 2 years ago

Thank you! (Especially as I gather it was your last day at Mapbox!)