Tuesday, May 17, 2016

In-Browser filtering of raster pixels in Leaflet, Part 2

A few days back I posted a link to L.TileLayer.PixelFilter This is a Leaflet TileLayer extension which rewrites the tiles after they have loaded, comparing each pixel against a set of pixel-codes and replacing the pixel with either a "matched" or "not matched" color. It's pretty useful for generating dynamic masks and highlights, if your back-end data is a raster and not vector data.

I had said that the client's request was to display plant hardiness zones, and that I was using the Unique Values colors as the RGB codes to match against zone codes. I lied. The reality was much more complicated than that, but would have been distracting from the end and the result. So here's the longer story.

The Rasters and the Requirement


The client has 3 rasters: Plant Hardiness Zones (PHZ), Precipitation classifications (PCP), Global Ecoregions (GEZ). Each of these is a worldwide dataset, and each has about 20 discrete integer values numbered 10 to 30ish.

The user selects multiple combination of these three factors, e.g. these two:
  • Zone 7 (PHZ=7)
  • Rainfall 20-30 inches per year (PCP=3)
  • Temperate oceanic forest (GEZ=31)

  • Zone 7 (PHZ=7)
  • Rainfall 20-30 inches per year (PCP=3)
  • Temperate continental forest (GEZ=32)
The user would then see any areas which match any of these "criteria trios" highlighted on the map. The idea is that a plant that's invasive in these areas, would also be invasive in the other areas highlighted on the map.

Fortunately, the rasters are not intended to be visible and do not need to be visually pleasing. They need to be either colored (the pixel matches any of the trios) or else transparent (not a match).

Raster To Tiles To Leaflet


Three variables and a need for one raster... sounds like we could have a three-band raster! I used ArcMap's Composite Bands tool to merge the three rasters into a three-band raster. Voila, one TIFF with three-part pixels such as (7, 3, 31) and (7, 3, 32) I just have to keep straight that R is PHZ, G is PCP, and B is GEZ.

Second, I needed to slice this up into map tiles. But it's very important that:
  • The tiles be in a format that preserves R, G, and B exactly. Something GIF or JPEG would be entirely unsuitable since GIF picks a 256-color palette, and JPEG is lossy and fudges together colors. TIFF on the other hand is not viewable in browsers as a map tile. But PNG is just perfect: RGB by default, and viewable in browsers.
  • The tile-slicing process (I picked gdal2tiles.py) must also preserve exact RGB values. The default average resampler uses interpolation, so a pixel in between a PCP=3 and PCP=2 would get PCP=2.5 and that's no good! Use the nearest neighbor resampling algorithm, which guarantees to use an existing pixel value.
Slicing up the TIFF into web-read PNGs was pretty straightforward:
gdal2tiles.py -z 0-7 -r near -t_srs epsg:3857 threeband.tif tiles

Point Leaflet at it, and I get some nearly-black tiles loading and displaying just as I expected. Well, after I remembered that gdal2tiles.py generates tiles in TMS numbering scheme, so I had to set tms:true in my L.TileLayer constructor.

I downloaded a few of the tiles and opened them up in GIMP and sure enough, that shade of black is actually (7,3,31) The tiles are not meant to be visually attractive... but those preserved RGB codes are beautiful to me.


Client-Side Pixel Detection and Swapping


That's the topic of my next posting: now that I have RGB tiles, how can I intercept them, compare them against a set of RGB codes, and appropriately color/clear pixels...? Turns out it was easier and more elegant than I had imagined.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.