Home

Awesome

H3J / H3T

Light H3 data formats for client side geometry generation and rendering using MapLibreGL

img

Why?

Because we, at Inspide, generate a huge amount of spatial data where the geometry is implicitly represented by its H3 index, and it makes no sense to waste time and resources generating, storing and sending the geometries downstream to the client.

The format

The first approach was to strip the data down to the bones and re-use vectortiles without geometries, processing the features after rendering. But (uppercase bold but), vectortiles specification drops the features with no geometry. So... back to the drawing table.

What about a headless CSVy format? It should be the most compact ascii format, but... If you send CSVy data and want to render it in a MapLibreGL map, you need to parse it into GeoJSON first, and parsing huge CSVs into JSON objects can be quite time consuming. And, on the other hand, once gzip or brotli is involved, the lack of text redundancy has no impact in the size of the file.

And what about PBFy the data? Then you'll need to PBFy it at the server and then de-PBFy it at client side to process it... so again, no gain at all.

So, say hello to H3J and its cousin H3T (tiled H3J) :wave:

{
    "metadata": {
        ...
    },
    "cells":[
        {
            "h3id":  '8c390cb1bcdb400',
            "property_1": 0,
            "property_2": 'potato'
        },
        {
            ...
        },
        {
            "h3id": '8c390cb1bcdb800',
            "property_1": 1,
            "property_2": 'tomato'
        }
    ]
}

So, H3J:

You can find the JSON schema for H3J here.

Let's compare file sizes with raw GeoJSON, using the included samples:

sample 1sample 2
# features49382477
GeoJSON1.8 MB884 kB
H3J252 kB127 kB
GeoJSON, gzipped216 kB109 kB
H3J, gzipped23 kB12 kB

So H3J files are ~ 7 times smaller than GeoJSON, and up to ~ 10 times smaller if comparing gzipped files. (Tested with 20 different data files)

And H3T? Same format, but is served using a ZXY endpoint and each .../z/x/y.h3t file contains all the H3 cells that fall within the linked quadkey tile.

Comparing the average (gzipped) tile size for zoom level 14, H3 levels 11 and 12 (tested with 500 tiles, avg.: 10437 features each)

MVTH3T
raw3.4 MB419 kB
gzipped206 kB34.6 kB

So H3T tiles are ~ 6 times smaller than MVT.

<!-- One of the side effects of `H3T` format is that **you can add object and array properties to your features!!** To do so using MVT you need to serialize the object server-side to add the info as a text property of the feature (as per MVT specs), and then deserialize it at the client in order to use the info within that property. -->

The MapLibreGL module

This module for MapLibre GL (starting with v1.14.1-rc.2) allows to generate H3 cells geometry client side from compact data and render & manage them there.

Now that you wanna use it... First of all

yarn install

Then, just import it it as any other Node module out there.

require('h3j-h3t')

If you want an UMD bundle, you need to build it first

yarn build

Then you can just import it in your JS code:

import 'dist/h3j_h3t.js';

Now what

Once imported, you will find three new methods in your maplibregl.Map object:

addH3JSource(source_name, source_options)

Adds a GeoJSONSource and load H3J data, all at once. Returns the maplibregl.Map instance as a promise.

map.addH3JSource(
    'h3j_testsource',
    {
      "data": 'data/sample_1.h3j'
    }
  )

Source options:

ParamDatatypeDescriptionDefault
geometry_typestringGeometry type at the output. Possible values are: Polygon (hex cells) and Point (cells centroids)Polygon
promoteIdbooleanWhether to use the H3 index as unique feature ID (default) or generate a bigint one based on that index. Default is faster and OGC compliant, but taking into account this issue you might want to set it to false depending on your use casetrue
httpsbooleanWhether to request the tiles using SSL or nottrue
datastring / objectURL to retrieve the H3J file or inlined H3J object
...anyThe same options that expects Map.addSource for geojson sources
timeoutintegerMax time in ms to wait for the data to be downloaded. 0 implies no limit0
debugbooleanWhether to send to console some metricsfalse

setH3JData(sourcename, data, [sourceoptions])

This method allows the user change the data rendered in any GeoJSONSource with data from an H3J inlined object or URL

map.setH3JData('h3j_testsource','data/sample_2.h3j');

Source options:

ParamDatatypeDescriptionDefault
geometry_typestringGeometry type at the output. Possible values are: Polygon (hex cells) and Point (cells centroids)Polygon
promoteIdbooleanWhether to use the H3 index as unique feature ID (default) or generate a bigint one based on that index. Default is faster and OGC compliant, but taking into account this issue you might want to set it to false depending on your use casetrue
httpsbooleanWhether to request the tiles using SSL or nottrue
...anyThe same options that expects Map.addSource for geojson sources
timeoutintegerMax time in ms to wait for the data to be downloaded. 0 implies no limit0
debugbooleanWhether to send to console some metricsfalse

addH3TSource(name, sourceoptions)

This method registers a custom protocol for h3tiles:// and adds a VectorTileSource that feeds on an .../z/x/y.h3t endpoint. Returns the maplibregl.Map instance as a promise.

map.addH3TSource(
    'h3j_testsource',
    {
      "tiles": ['h3tiles://example.com/z/x/y.h3t']
    }
  )

Source options:

ParamDatatypeDescriptionDefault
geometry_typestringGeometry type at the output. Possible values are: Polygon (hex cells) and Point (cells centroids)Polygon
promoteIdbooleanWhether to use the H3 index as unique feature ID (default) or generate a bigint one based on that index. Default is faster and OGC compliant, but taking into account this issue you might want to set it to false depending on your use casetrue
httpsbooleanWhether to request the tiles using SSL or nottrue
sourcelayerstringThe name of the layer within the vector tile that will be rendered
tiles[text]URL of the H3T endpoint, using h3tiles:// protocol
...anyThe same options that expects Map.addSource for vector sources
timeoutintegerMax time in ms to wait for the data to be downloaded. 0 implies no limit0
debugbooleanWhether to send to console some metrics per tilefalse

Benchmarks

H3J

Average overhead time of using H3J instead of loading a good ol'GeoJSON. For 100 runs of setH3JData:

H3Jsample 1sample 2
# features49382477
overhead68 ms37 ms
overhead per cell0.014 ms0.015 ms

H3T

Average values for H3T rendering. For 500 tiles at zoom level 14, rendering H3 cells with levels 11 and 12:

cells per tile10437
overhead per tile261 ms
overhead per cell0.025 ms

Examples