GanosBase includes a geospatial grid engine that encodes the Earth's surface as a multi-resolution hexagonal grid. Use H3 grids to aggregate spatial point data, run grid-distance queries, and render per-cell heatmaps — all within a single SQL statement. This page walks through a complete end-to-end workflow using Uber's New York Taxi Pickup dataset: set up the schema, import data from OSS, encode pickup points as H3 cells, aggregate into a heatmap, query by grid distance, and visualize results as vector tiles.
How H3 grids work
A geospatial grid divides the Earth's surface into polygon cells at progressively finer resolutions. Each cell has a unique code, making it straightforward to attach location context to any point, line, or area and join it with other spatiotemporal data.
GanosBase supports two grid types:
GeoSOT — A discrete, multi-scale location identification system based on China's geospatial subdivision standard.
H3 — A global hexagonal grid system developed by Uber, organized into hierarchical resolution levels.
H3's hexagonal structure gives it three useful properties compared to square or triangular grids:
| Property | Description |
|---|---|
| Uniform distribution | Each cell covers an approximately equal area, reducing boundary distortion at scale |
| Fixed neighbor count | Every hexagon has exactly six equidistant neighbors, simplifying movement analysis and range queries |
| Directionlessness | No preferred axis, so spatial aggregation is unbiased across directions |
GanosBase extends H3 with degradation (degenerated grid computing), which represents a spatial range using a minimal combination of cells at different resolutions instead of a single dense layer of fine-grained cells. This reduces the number of encoded cells needed to cover a region, lowering storage costs.

Use cases
Logistics and travel — Route planning, delivery range delineation, area coverage analysis, and hotspot discovery.
Data analysis — Population density analysis, mobile user behavior analysis, and geographic market segmentation.
Internet of Things (IoT) — Spatial distribution analysis for smart city monitoring, environmental sensing, and asset tracking.
Social networks — Location-based services (LBS), friend location sharing, and event notifications.
Emergency response — Disaster distribution mapping, emergency resource allocation, and rescue area delineation.
Capabilities
| Capability | Description |
|---|---|
| Encoding | Convert latitude/longitude coordinates, geometry types (point, line, polygon), standard H3 strings, integer H3 codes, or binary H3 codes into H3 grid cells |
| Hierarchy traversal | Navigate parent-child relationships between grid resolutions and compute paths between cells |
| Spatial querying | Query by grid distance, bounding region, or neighbor rings |
| Degradation | Represent a spatial range with a minimal set of cells across resolutions to reduce storage |
| Geometry interop | Convert H3 cells to the GanosBase geometry type for joint analysis with vector data |
| Raster interop | Compute pixel statistics over raster data aggregated by H3 cell |
| Visualization | Generate Mapbox Vector Tiles (MVT) directly from H3 cells for front-end rendering |
GanosBase also includes a self-developed geospatial grid index that accelerates grid code lookups and aggregate calculations.
For the full function reference, see GeomGrid SQL Reference.
Advantages over open-source H3
| Area | GanosBase H3 |
|---|---|
| Encoding inputs | Convert GanosBase point, line, and surface geometry types directly to H3 cells without intermediate steps |
| Performance | Improved encoding throughput and grid query efficiency compared to standard H3 extensions |
| Cross-model analysis | Query H3 grids alongside GanosBase geometry or raster models in a single SQL statement |
| Large-scale storage | PolarDB for PostgreSQL's polymorphic tiered storage lets you encode and persist large point datasets in Object Storage Service (OSS), significantly reducing storage costs |
Best practices: taxi pickup heatmap
This walkthrough uses Uber's 2023 New York Taxi Pickup Dataset (FOIL) to demonstrate the full H3 workflow: import raw CSV data from OSS, encode pickup points as H3 cells at resolution 13, aggregate into a per-cell count table, run a grid-distance query, and render an interactive heatmap using Mapbox Vector Tiles.
Prerequisites
Before you begin, make sure you have:
A running PolarDB for PostgreSQL instance with GanosBase enabled
An OSS bucket containing the FOIL CSV file (see Upload objects)
Database superuser or extension-creation privileges
Set up extensions and schema
Install the GeomGrid extension. This also installs any required dependencies.
CREATE EXTENSION ganos_geomgrid CASCADE;Create the data table. The
h3gridfield type stores H3 cell codes. The fieldh3_lev13holds the 13th level H3 code. Different levels of H3 grids have different resolutions. You can define the resolutions based on your business needs. For the spatial resolution corresponding to each level of H3, see the H3 resolution table.-- h3_lev13 stores the resolution 13 H3 cell for each pickup point CREATE TABLE FOIL2013 ( id text, lon float, lat float, h3_lev13 h3grid );
Import data from OSS
The FOIL dataset is a CSV file. Use the GanosBase foreign data wrapper (FDW) to load it directly from OSS without a separate ETL step.
Install the FDW extension.
CREATE EXTENSION ganos_fdw CASCADE;Create a server that points to the OSS CSV file. Replace the placeholders with your OSS credentials and bucket path. For
datasourceformat details, see .CREATE SERVER csvserver FOREIGN DATA WRAPPER ganos_fdw OPTIONS ( datasource 'OSS://<access_id>:<secrect_key>@[<Endpoint>]/<bucket>/path_to/file.csv', format 'CSV' ); CREATE USER MAPPING FOR CURRENT_USER SERVER csvserver OPTIONS (user '<access_id>', password '<secrect_key>');Map the CSV columns to a foreign table. The query interface is identical to a regular table.
CREATE FOREIGN TABLE trip_data_1 ( medallion varchar, pickup_longitude varchar, pickup_latitude varchar ) SERVER csvserver OPTIONS (layer 'trip_data_1');Verify the foreign table returns data.
SELECT * FROM trip_data_1;Insert the CSV data into
FOIL2013.INSERT INTO FOIL2013 SELECT medallion AS id, CAST(pickup_longitude AS double precision) AS lon, CAST(pickup_latitude AS double precision) AS lat FROM trip_data_1;Verify the import.
SELECT * FROM FOIL2013;
Encode pickup points as H3 cells
GanosBase supports several encoding functions:
| Function | Input |
|---|---|
ST_H3FromLatLng | Latitude, longitude, and resolution |
ST_H3FromText | Standard H3 string |
ST_H3FromInt | Integer H3 code |
ST_AsH3Grid | GanosBase geometry type |
Use ST_H3FromLatLng to encode every pickup point at resolution 13.
-- Populate h3_lev13 from the lat/lon columns at resolution 13
UPDATE FOIL2013
SET h3_lev13 = ST_H3FromLatLng(lat, lon, 13);
-- Inspect the encoded cells
SELECT id, lon, lat, ST_AsText(h3_lev13) AS h3
FROM FOIL2013
LIMIT 100;Aggregate pickups into a heatmap
Group pickup points by H3 cell to get a per-cell count. This count table is the basis for the heatmap visualization in the next step.
-- Count pickups per resolution 13 cell
CREATE TABLE h3_count_lev13 AS
SELECT
ST_AsText(h3_lev13) AS h3code,
COUNT(*) AS count
FROM FOIL2013
GROUP BY h3_lev13;
-- Preview the busiest cells
SELECT ST_AsText(h3_lev13), ST_AsText(geometry), count
FROM h3_count_lev13
ORDER BY count DESC;Query by grid distance
ST_GridDistance returns the number of grid steps between two H3 cells at the same resolution — similar to a hop count on a graph. The following query returns all pickup records within 10 grid steps of the coordinate (40.71481749, -73.99100368).
SELECT *
FROM foil2013
WHERE ST_GridDistance(
ST_H3FromLatLng(40.71481749, -73.99100368, 13),
h3_lev13
) < 10;For the full ST_GridDistance reference, see ST_GridDistance.
Visualize H3 grids as vector tiles
GanosBase renders H3 cells as Mapbox Vector Tiles (MVT), letting you display per-cell statistics on an interactive map. The database serves tiles on demand; the front end colors each cell by pickup count.
GanosBase also supports rendering dynamically generated H3 cells that are not pre-stored in a table (using ST_AsMVTGeom(ST_H3FromLatLng(lat, lon, 10), ...)). Pre-storing cells, as in this example, is faster for repeated queries.Create a GIST index on the
h3_lev13column to speed up spatial tile queries.CREATE INDEX ON h3_count_lev13 USING GIST(h3_lev13);Retrieve the vector tile for tile coordinates
(z=14, x=4826, y=6157). Each tile request from the front end calls a query in this form.SELECT ST_AsMVT(tile) FROM ( SELECT ST_AsMVTGeom( h3_lev13, ST_Transform(ST_TileEnvelope(14, 4826, 6157), 4326) ) AS grid, count FROM h3_count_lev13 WHERE h3_lev13 && ST_Transform(ST_TileEnvelope(14, 4826, 6157), 4326) ) AS tile;Start the Python tile server and open
http://localhost:5100in your browser. The front end queries the database in real time as you pan and zoom the map, coloring each H3 cell by pickup count.
The front end requires only the two files in the Appendix — a Python script and an HTML file. Run the Python script to start the tile server on port 5100.
What's next
As the world's first database to support mobile objects (MOD), GanosBase's geospatial grid capabilities have been validated in production across transportation, logistics, travel, and automotive industries. The H3 grid engine integrates natively with GanosBase trajectory, vector, and raster models — unlocking further spatiotemporal analysis workflows beyond what this guide covers.
GeomGrid SQL Reference — Full function reference for all GeomGrid operations
ST_H3FromLatLng — Encode a point at a given H3 resolution
ST_GridDistance — Compute the grid-hop distance between two H3 cells
ST_AsH3Grid — Convert a geometry type directly to H3 cells
Appendix
Python tile server
from quart import Quart, send_file, render_template
import asyncpg
import io
import re
## Database connection parameters
CONNECTION = {"host": "YOUR-HOST-NAME-OR-IP", "port": PORT_NO, "database": "DATABASE_NAME",
"user": "USER_NAME", "password": "PASSWORD"}
## Target table name/field/ID
TABLE = "h3_count_lev13"
H3_COL = "h3_lev13"
H3_GEOM_COL = "geometry"
AGG_VAL_COL = "count"
COL_SRID = 4326
app = Quart(__name__, template_folder='./')
@app.before_serving
async def create_db_pool():
app.db_pool = await asyncpg.create_pool(**CONNECTION)
@app.after_serving
async def close_db_pool():
await app.db_pool.close()
@app.route("/")
async def home():
sql = f'''
SELECT ST_Extent(ST_Transform(ST_Envelope({H3_GEOM_COL}), 4326))
FROM {TABLE};
'''
async with app.db_pool.acquire() as connection:
box = await connection.fetchval(sql)
box = re.findall('BOX\((.*?) (.*?),(.*?) (.*?)\)', box)[0]
min_x, min_y, max_x, max_y = list(map(float, box))
bounds = [[min_x, min_y], [max_x, max_y]]
center = [(min_x + max_x) / 2, (min_y + max_y) / 2]
return await render_template('./index.html', center=str(center), bounds=str(bounds))
@app.route("/h3_mvt/<int:z>/<int:x>/<int:y>")
async def h3_mvt(z, x, y):
sql = f'''
SELECT ST_AsMVT(tile.*)
FROM
(SELECT ST_AsMVTGeom({H3_COL}, ST_Transform(ST_TileEnvelope($1,$2,$3),{COL_SRID}), 4096, 512, true) geometry,
{AGG_VAL_COL} count
FROM {TABLE}
WHERE ({H3_COL} && ST_Transform(ST_TileEnvelope($1,$2,$3),{COL_SRID}))) tile'''
async with app.db_pool.acquire() as connection:
tile = await connection.fetchval(sql, z, x, y)
return await send_file(io.BytesIO(tile), mimetype='application/vnd.mapbox-vector-tile')
if __name__ == "__main__":
app.run(port=5100)index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>map viewer</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.4.2/chroma.min.js"></script>
</head>
<body>
<div id="map" style="position: absolute;left:0; top: 0; bottom: 0; width: 100%;cursor:pointer;"></div>
<div class="counter"
style="position: absolute;left:2%;font-size: 20px;padding: .1em .1em;text-shadow: 3px 3px 3px black;">
<span>Current grid count:</span>
<span id="count">0</span>
</div>
<script>
let YOUR_TOKEN = "pk.eyJ1Ijoia3pmaWxlIiwiYSI6ImNqbHZueXdlZjB2cG4zdnFucGl1OHJsMjkifQ.kW_Utrh8ETQltRk6fnpa_A"
mapboxgl.accessToken = YOUR_TOKEN;
const map = new mapboxgl.Map({
container: "map",
style: "mapbox://styles/mapbox/navigation-night-v1",
center: {{ center }},
zoom: 1
})
map.on("load", () => {
map.fitBounds({{ bounds }})
map.on('mousemove', 'h3', (e) => {
map.getCanvas().style.cursor = "default";
if (e.features.length > 0)
document.getElementById('count').innerText = e.features[0].properties.count
})
map.on('mouseleave', 'h3', () => {
map.getCanvas().style.cursor = "grab";
document.getElementById('count').innerText = 0
})
map.addSource("h3_source", {
type: "vector",
tiles: [`${window.location.href}h3_mvt/{z}/{x}/{y}`],
tileSize: 512
});
// make color map
const MIN = 1
const MAX = 600
const STEP = 10
color_map = chroma.scale(["#536edb", "#5d96a5", "#68be70", "#91d54d", "#cddf37", "#fede28", "#fda938", "#fb7447", "#f75a40", "#f24734", "#e9352a", "#da2723", "#cb181d"])
.domain([MIN, MAX]);
let colors = []
for (let i = MIN; i < MAX; i += STEP)
colors.push(color_map(i).hex(), i)
colors.push(color_map(MAX).hex())
map.addLayer({
id: "h3",
type: "fill",
source: "h3_source",
"source-layer": "default",
paint: {
"fill-color": [
"step", ["get", "count"],
...colors
],
"fill-opacity": 0.8
}
});
});
</script>
</body>
</html>