Skip to content

geozelot/fgb-vt

Repository files navigation

npm version license node types bundle size

fgb-vt

Native Node/TypeScript library for high-performance Vector Tile layering, encoding and serving - directly from FlatGeobuf files. Fully utilizes FlatGeobuf's Packed Hilbert R-tree for tile-sized byte-range reads over the main feature storage.

fgb-vt attempts to conceptually close the gap between full tile pyramid creation on the backend, database-driven tile servers and processing static datasets on the client. It is specifically designed to access large, single-file objects on cloud storage via load-efficient (HTTP) Range Requests and stateless deployment (e.g. via AWS Lambda) - targeting highly dynamic geospatial big-data mapping requirements.

Under the hood it's binary all the way down - no GeoJSON detour, no intermediate format, just FlatBuffers in and Protobuf out, with projection, clipping and simplification natively implemented at the lowest level the types allow.

fgb-vt supports multi-layered tiles via concurrent access to different sources - even across different storage backends. It follows a slim, three-tier API layer approach for different use cases:

  • TileServer - a semi-stateful, lazy-caching tile server over an initially configured set of sources
  • TileClient - semi-stateful, connection-optimized tile client middleware with dynamic source selection
  • tile() - semi-stateless, request-scoped, one-off tile generation function, suitable for Lambda-driven setups

and includes a Browser bundle for client-side HTTP(S) source access.

Shipped with three concurrency-optimized connectors:

  • LocalConnector - for local filesystem access
  • HttpConnector - for HTTP(S) with Range Requests
  • S3Connector - for Amazon S3 / compatible storage backends with Byte Range reads

Quick Start

npm install @geozelot/fgb-vt
import { TileClient, LocalConnector } from '@geozelot/fgb-vt';

// set up a client with a filesystem connector
const client = new TileClient(new LocalConnector());

// request tile z=4, x=4, y=6 - sources are passed per call
const pbf = await client.tile(4, 4, 6, {
  name: 'counties',
  path: './data/us_counties.fgb',
});

// pbf is a ready-to-serve Uint8Array (MVT/PBF encoded)
await client.close();


Installation

```bash
npm install @geozelot/fgb-vt
```

For S3 support, add the optional peer dependency:

```bash
npm install @aws-sdk/client-s3
```

Browser

```html
<!-- UMD -->
<script src="https://unpkg.com/@geozelot/fgb-vt/dist/fgb-vt.umd.min.js"></script>

<!-- ESM -->
<script type="module">
  import { tile, HttpConnector } from 'https://unpkg.com/@geozelot/fgb-vt/dist/fgb-vt.esm.min.js';
</script>
```

Note: Browser builds ship with HttpConnector only!


Usage

TileServer

Bind connectors and sources once; call tile() for the life of the process. Headers and spatial index metadata are lazily cached on first access - maximum throughput after warm-up.

  • Single connector:

    import { TileServer, LocalConnector } from '@geozelot/fgb-vt';
    
    const server = new TileServer({
      connector: new LocalConnector(),
      sources: { name: 'counties', path: './data/us_counties.fgb' },
    });
    
    const pbf = await server.tile(4, 4, 6);
    const meta = await server.tileJSON();   // TileJSON 3.0.0
    await server.close();
  • Multi source (layered tiles):

    const server = new TileServer({
      connector: new LocalConnector(),
      sources: [
        { name: 'buildings', path: './data/buildings.fgb' },
        { name: 'roads', path: './data/roads.fgb', options: { maxZoom: 16 } },
      ],
    });
  • Multi connector:

    const server = new TileServer([
      { connector: new LocalConnector(), sources: { name: 'local', path: './local.fgb' } },
      { connector: new HttpConnector(), sources: { name: 'remote', path: 'https://cdn.example.com/remote.fgb' } },
    ]);

TileClient

Connector bound at construction; sources provided per call. One connector, varying datasets - well suited for middleware or request-scoped source selection.

  • Single source:

    import { TileClient, HttpConnector } from '@geozelot/fgb-vt';
    
    const client = new TileClient(
      new HttpConnector({ headers: { Authorization: 'Bearer ...' } }),
    );
    
    const pbf = await client.tile(14, 8192, 5461, {
      name: 'parcels', path: 'https://data.example.com/parcels.fgb',
    });
    await client.close();
  • Multi source (layered tiles):

    const pbf = await client.tile(12, 2048, 1365, [
      { name: 'water', path: '/data/water.fgb' },
      { name: 'roads', path: '/data/roads.fgb' },
    ]);

tile()

Everything per call - connector, coordinates, sources. No instance state beyond a module-level tile bounds cache. Drop it into a Lambda and call it a day.

  • Single source:

    import { tile, LocalConnector } from '@geozelot/fgb-vt';
    
    const connector = new LocalConnector();
    const pbf = await tile(connector, 14, 8192, 5461, {
      name: 'poi', path: './data/poi.fgb',
    });
    await connector.close();
  • Multi source:

    const pbf = await tile(connector, 14, 8192, 5461, [
      { name: 'buildings', path: './data/buildings.fgb' },
      { name: 'roads', path: './data/roads.fgb' },
    ]);

Browser bundle

Use the browser bundle to turn any hosted .fgb into a vector tile source - no tile server required:

<script src="https://unpkg.com/@geozelot/fgb-vt/dist/fgb-vt.umd.min.js"></script>
<script>
  const client = new fgbvt.TileClient(new fgbvt.HttpConnector());

  // generate tiles on demand, client-side
  const pbf = await client.tile(14, 8192, 5461, {
    name: 'counties', path: 'https://data.example.com/counties.fgb',
  });
</script>

API Reference

Connectors

Connectors abstract concurrent byte-range I/O across storage backends. Each implements the Connector interface.

Connector Reads from Path format
LocalConnector filesystem ./data/buildings.fgb
HttpConnector HTTP(S) with Range Requests https://cdn.example.com/roads.fgb
S3Connector Amazon S3 / compatible s3://bucket/key.fgb
new LocalConnector({ maxOpenFiles: 64 })

new HttpConnector({
  headers: { Authorization: 'Bearer ...' },
  timeout: 30_000,
  maxConcurrency: 6,
  retry: { attempts: 3, backoff: 200 },
})

new S3Connector({
  region: 'us-east-1', 
  credentials: { accessKeyId: '...', secretAccessKey: '...' }, 
  maxConcurrency: 6,
  endpoint: 'http://localhost:9000'   // for S3-compatible storage backends
})

Options

Options cascade through three levels - source overrides tile-level defaults overrides built-in defaults:

Option Default Description
extent 4096 Tile coordinate extent
buffer 64 Buffer around tile in tile-coordinate pixels
tolerance 3 Douglas-Peucker simplification tolerance
minZoom 0 Skip source below this zoom
maxZoom 24 Skip source above this zoom
const server = new TileServer(
  {
    connector: new LocalConnector(),
    sources: [
      { name: 'detail', path: './detail.fgb', options: { tolerance: 1 } },  // tolerance=1
      { name: 'overview', path: './overview.fgb' },                         // tolerance=5 (from tile defaults)
    ],
  },
  { tolerance: 5, maxZoom: 18 },  // tile-level defaults
);

Types

import type {
  Connector,
  Source, SourceOptions, TileOptions,
  TileServerLayer, TileJSON, BBox,
  LocalConnectorOptions, HttpConnectorOptions, S3ConnectorOptions,
} from '@geozelot/fgb-vt';

Testing

Unit Tests

npm test

Runs the full test suite via Vitest.

Benchmarks

npm run bench
Stage Input Throughput
Projection 1,000 coord pairs ~38k ops/s
Clipping 50 polygons (20v) ~22k ops/s
Simplification 200-point line ~91k ops/s
MVT Encoding 100-point line ~1.6M ops/s
PBF Encoding 100-feature layer ~17k ops/s
Full Pipeline 100 mixed features ~2.6k ops/s

License

MIT