From f20f6dc4b8f049e25d89123c44f0fd859b0ed52c Mon Sep 17 00:00:00 2001 From: Ranier Montalbo Date: Wed, 12 May 2021 18:58:41 +0800 Subject: [PATCH 01/24] Add #92 - Generate exact single tile --- src/index.js | 30 +- test/fixtures/single-tile.json | 707 +++++++++++++++++++++++++++++++++ test/test-get-single-tile.js | 41 ++ 3 files changed, 773 insertions(+), 5 deletions(-) create mode 100644 test/fixtures/single-tile.json create mode 100644 test/test-get-single-tile.js diff --git a/src/index.js b/src/index.js index f96e63c..0040efe 100644 --- a/src/index.js +++ b/src/index.js @@ -5,18 +5,21 @@ import wrap from './wrap.js'; // date line processing import transform from './transform.js'; // coordinate transformation import createTile from './tile.js'; // final simplified tile generation -const defaultOptions = { +const defaultTileOptions = { maxZoom: 14, // max zoom to preserve detail on - indexMaxZoom: 5, // max zoom in the tile index - indexMaxPoints: 100000, // max number of points per tile in the tile index tolerance: 3, // simplification tolerance (higher means simpler) extent: 4096, // tile extent buffer: 64, // tile buffer on each side - lineMetrics: false, // whether to calculate line metrics + lineMetrics: false // whether to calculate line metrics +}; + +const defaultOptions = extend(Object.create(defaultTileOptions), { + indexMaxZoom: 5, // max zoom in the tile index + indexMaxPoints: 100000, // max number of points per tile in the tile index promoteId: null, // name of a feature property to be promoted to feature.id generateId: false, // whether to generate feature ids. Cannot be used with promoteId debug: 0 // logging level (0, 1 or 2) -}; +}); class GeoJSONVT { constructor(data, options) { @@ -214,3 +217,20 @@ function extend(dest, src) { export default function geojsonvt(data, options) { return new GeoJSONVT(data, options); } + +export function geoJSONToTile(data, z, x, y, options, shouldWrap = false, shouldClip = false) { + options = extend(Object.create(defaultTileOptions), options); + + let features = convert(data, options); + if (shouldWrap) { + features = wrap(features, options); + } + if (shouldClip || options.lineMetrics) { + const pow2 = 1 << z; + const buffer = options.buffer / options.extent; + const left = clip(features, pow2, (x - buffer), (x + 1 + buffer), 0, -1, 2, options); + features = clip(left || [], pow2, (y - buffer), (y + 1 + buffer), 1, -1, 2, options); + } + + return features ? transform(createTile(features, z, x, y, options), options.extent) : null; +} diff --git a/test/fixtures/single-tile.json b/test/fixtures/single-tile.json new file mode 100644 index 0000000..de71d19 --- /dev/null +++ b/test/fixtures/single-tile.json @@ -0,0 +1,707 @@ +{ + "type": "Feature", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -77.066104, + 38.910203 + ], + [ + -77.066106, + 38.910321 + ], + [ + -77.066112, + 38.910758 + ], + [ + -77.065249, + 38.910775 + ], + [ + -77.065178, + 38.910793 + ], + [ + -77.065139, + 38.910804 + ], + [ + -77.064904, + 38.91036 + ], + [ + -77.064855, + 38.910268 + ], + [ + -77.064621, + 38.909825 + ], + [ + -77.06459, + 38.909766 + ], + [ + -77.064342, + 38.909298 + ], + [ + -77.064281, + 38.909182 + ], + [ + -77.064201, + 38.909226 + ], + [ + -77.064175, + 38.909235 + ], + [ + -77.064149, + 38.909241 + ], + [ + -77.064122, + 38.909244 + ], + [ + -77.06336, + 38.90926 + ], + [ + -77.061442, + 38.909288 + ], + [ + -77.060801, + 38.909297 + ], + [ + -77.059237, + 38.909315 + ], + [ + -77.058186, + 38.90933 + ], + [ + -77.057113, + 38.909343 + ], + [ + -77.055623, + 38.909368 + ], + [ + -77.054762, + 38.909377 + ], + [ + -77.053951, + 38.909389 + ], + [ + -77.053904, + 38.909393 + ], + [ + -77.053858, + 38.909399 + ], + [ + -77.053813, + 38.909408 + ], + [ + -77.052833, + 38.909635 + ], + [ + -77.052799, + 38.909642 + ], + [ + -77.052772, + 38.909645 + ], + [ + -77.052692, + 38.90965 + ], + [ + -77.052443, + 38.909649 + ], + [ + -77.05096, + 38.909639 + ], + [ + -77.050327, + 38.909634 + ], + [ + -77.049545, + 38.909631 + ], + [ + -77.049533, + 38.909635 + ], + [ + -77.049341, + 38.909635 + ], + [ + -77.048903, + 38.909635 + ], + [ + -77.048797, + 38.909634 + ], + [ + -77.04773, + 38.909639 + ], + [ + -77.046632, + 38.90964 + ], + [ + -77.045758, + 38.909641 + ], + [ + -77.044877, + 38.909643 + ], + [ + -77.044578, + 38.909641 + ], + [ + -77.044493, + 38.909635 + ], + [ + -77.04442, + 38.909626 + ], + [ + -77.044345, + 38.909608 + ], + [ + -77.044283, + 38.909578 + ], + [ + -77.044249, + 38.909541 + ], + [ + -77.044212, + 38.909496 + ], + [ + -77.044199, + 38.909458 + ], + [ + -77.044182, + 38.909421 + ], + [ + -77.044162, + 38.909385 + ], + [ + -77.04414, + 38.90935 + ], + [ + -77.044113, + 38.909313 + ], + [ + -77.044083, + 38.90928 + ], + [ + -77.044051, + 38.909247 + ], + [ + -77.044018, + 38.909165 + ], + [ + -77.044018, + 38.909117 + ], + [ + -77.044029, + 38.909071 + ], + [ + -77.044037, + 38.909038 + ], + [ + -77.044058, + 38.908981 + ], + [ + -77.043996, + 38.909021 + ], + [ + -77.043919, + 38.909052 + ], + [ + -77.043854, + 38.909067 + ], + [ + -77.043798, + 38.909073 + ], + [ + -77.043755, + 38.909075 + ], + [ + -77.043689, + 38.909056 + ], + [ + -77.043656, + 38.909048 + ], + [ + -77.04359, + 38.909036 + ], + [ + -77.043527, + 38.909028 + ], + [ + -77.043486, + 38.909026 + ], + [ + -77.043444, + 38.909025 + ], + [ + -77.043402, + 38.909026 + ], + [ + -77.043344, + 38.909029 + ], + [ + -77.043286, + 38.909037 + ], + [ + -77.043263, + 38.909041 + ], + [ + -77.04323, + 38.909047 + ], + [ + -77.043194, + 38.909056 + ], + [ + -77.043022, + 38.909118 + ], + [ + -77.042995, + 38.909132 + ], + [ + -77.04296, + 38.909151 + ], + [ + -77.042938, + 38.909165 + ], + [ + -77.042915, + 38.90918 + ], + [ + -77.042875, + 38.909209 + ], + [ + -77.04284, + 38.909239 + ], + [ + -77.042823, + 38.909255 + ], + [ + -77.042791, + 38.909288 + ], + [ + -77.042774, + 38.909307 + ], + [ + -77.042712, + 38.909315 + ], + [ + -77.042637, + 38.909322 + ], + [ + -77.042551, + 38.909317 + ], + [ + -77.042422, + 38.909299 + ], + [ + -77.041676, + 38.90903 + ], + [ + -77.039935, + 38.908425 + ], + [ + -77.038503, + 38.907925 + ], + [ + -77.037656, + 38.907632 + ], + [ + -77.037477, + 38.907534 + ], + [ + -77.037317, + 38.907438 + ], + [ + -77.037238, + 38.90739 + ], + [ + -77.037121, + 38.907315 + ], + [ + -77.036997, + 38.907233 + ], + [ + -77.036875, + 38.907137 + ], + [ + -77.036833, + 38.907094 + ], + [ + -77.036817, + 38.907081 + ], + [ + -77.036795, + 38.907068 + ], + [ + -77.036774, + 38.907058 + ], + [ + -77.036767, + 38.907052 + ], + [ + -77.036712, + 38.907034 + ], + [ + -77.03669, + 38.907028 + ], + [ + -77.036643, + 38.907022 + ], + [ + -77.03661, + 38.907021 + ], + [ + -77.03653, + 38.907021 + ], + [ + -77.036474, + 38.907021 + ], + [ + -77.03643, + 38.907025 + ], + [ + -77.03639, + 38.907033 + ], + [ + -77.036295, + 38.907058 + ], + [ + -77.036005, + 38.906995 + ], + [ + -77.035439, + 38.906869 + ], + [ + -77.035142, + 38.906757 + ], + [ + -77.035049, + 38.906725 + ], + [ + -77.0348, + 38.906641 + ], + [ + -77.034673, + 38.906594 + ], + [ + -77.034568, + 38.906558 + ], + [ + -77.034059, + 38.906382 + ], + [ + -77.033931, + 38.906338 + ], + [ + -77.032623, + 38.905883 + ], + [ + -77.03129, + 38.905426 + ], + [ + -77.030031, + 38.904982 + ], + [ + -77.029623, + 38.904835 + ], + [ + -77.028082, + 38.904304 + ], + [ + -77.027679, + 38.904167 + ], + [ + -77.02705, + 38.903949 + ], + [ + -77.026409, + 38.903729 + ], + [ + -77.025984, + 38.903579 + ], + [ + -77.024321, + 38.902997 + ], + [ + -77.024139, + 38.902954 + ], + [ + -77.023952, + 38.902954 + ], + [ + -77.023188, + 38.902956 + ], + [ + -77.022699, + 38.902958 + ], + [ + -77.021917, + 38.90296 + ], + [ + -77.021918, + 38.902902 + ], + [ + -77.021918, + 38.902521 + ], + [ + -77.019907, + 38.902521 + ], + [ + -77.018928, + 38.902521 + ], + [ + -77.016176, + 38.902519 + ], + [ + -77.015177, + 38.902518 + ], + [ + -77.013722, + 38.902514 + ], + [ + -77.012171, + 38.902516 + ], + [ + -77.011237, + 38.902516 + ], + [ + -77.009846, + 38.902515 + ], + [ + -77.009117, + 38.902514 + ], + [ + -77.009126, + 38.901346 + ], + [ + -77.00912, + 38.900203 + ], + [ + -77.009062, + 38.900203 + ], + [ + -77.008975, + 38.900203 + ], + [ + -77.008004, + 38.900198 + ], + [ + -77.00631, + 38.900193 + ], + [ + -77.005531, + 38.900197 + ], + [ + -77.002929, + 38.9002 + ], + [ + -77.002038, + 38.900203 + ], + [ + -77.001892, + 38.900203 + ], + [ + -77.000571, + 38.900205 + ], + [ + -76.999507, + 38.900204 + ], + [ + -76.998442, + 38.900204 + ], + [ + -76.998369, + 38.900204 + ], + [ + -76.996167, + 38.900205 + ], + [ + -76.994961, + 38.900203 + ], + [ + -76.994962, + 38.899748 + ], + [ + -76.994961, + 38.899626 + ], + [ + -76.994961, + 38.899367 + ], + [ + -76.994961, + 38.898908 + ], + [ + -76.994962, + 38.898447 + ] + ] + }, + "properties": { + "name": "P Street Northwest - Massachusetts Avenue Northwest" + } +} \ No newline at end of file diff --git a/test/test-get-single-tile.js b/test/test-get-single-tile.js new file mode 100644 index 0000000..d433a2a --- /dev/null +++ b/test/test-get-single-tile.js @@ -0,0 +1,41 @@ +import test from 'tape'; +import fs from 'fs'; +import path from 'path'; +import {geoJSONToTile} from '../src/index.js'; + +const square = [{ + geometry: [[[4160, -64], [4160, 4160], [-64, 4160], [-64, -64], [4160, -64]]], + type: 3, + tags: {name: 'Pennsylvania', density: 284.3}, + id: '42' +}]; + +test('geoJSONToTile: converts all geometries to a single vector tile', (t) => { + const geojson = getJSON('single-tile.json'); + const tile = geoJSONToTile(geojson, 12, 1171, 1566); + + t.equal(tile.features.length, 1, 'z12-1171-1577'); + t.equal(tile.features[0].tags.name, 'P Street Northwest - Massachusetts Avenue Northwest'); + + t.end(); +}); + +test('geoJSONToTile: clips geometries outside the tile', (t) => { + const geojson = getJSON('us-states.json'); + + const tile1 = geoJSONToTile(geojson, 7, 37, 48, {}, false, true); + t.same(tile1.features, getJSON('us-states-z7-37-48.json'), 'z7-37-48'); + + const tile2 = geoJSONToTile(geojson, 9, 148, 192, {}, false, true); + t.same(tile2.features, square, 'z9-148-192 (clipped square)'); + + t.equal(geoJSONToTile(geojson, 11, 800, 400, {}, false, true), null, 'non-existing tile'); + t.equal(geoJSONToTile(geojson, -5, 123.25, 400.25, {}, false, true), null, 'invalid tile'); + t.equal(geoJSONToTile(geojson, 25, 200, 200, {}, false, true), null, 'invalid tile'); + + t.end(); +}); + +function getJSON(name) { + return JSON.parse(fs.readFileSync(path.join(__dirname, `/fixtures/${ name}`))); +} From 65bf0cfcf01a6ad6dfb1f4f86d7f936556339b38 Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 10:46:56 -0800 Subject: [PATCH 02/24] Simplify diff --- src/geojsonvt.ts | 2 +- test/fixtures/single-tile.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/geojsonvt.ts b/src/geojsonvt.ts index 642a510..7a101b3 100644 --- a/src/geojsonvt.ts +++ b/src/geojsonvt.ts @@ -247,7 +247,7 @@ export class GeoJSONVT { /** * Invalidates (removes) tiles affected by the provided features * @internal - * @param features + * @param features */ invalidateTiles(features: GeoJSONVTInternalFeature[]) { const options = this.options; diff --git a/test/fixtures/single-tile.json b/test/fixtures/single-tile.json index de71d19..6e846d1 100644 --- a/test/fixtures/single-tile.json +++ b/test/fixtures/single-tile.json @@ -704,4 +704,4 @@ "properties": { "name": "P Street Northwest - Massachusetts Avenue Northwest" } -} \ No newline at end of file +} From fbf177aca96600b2393dc7fe5439f058a069df8f Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 10:50:42 -0800 Subject: [PATCH 03/24] Convert geoJSONToTile tests from tape to vitest --- package-lock.json | 11 +++++++++- src/geojsonvt.ts | 4 ++-- test/get-single-tile.test.ts | 38 +++++++++++++++++++++++++++++++++ test/test-get-single-tile.js | 41 ------------------------------------ 4 files changed, 50 insertions(+), 44 deletions(-) create mode 100644 test/get-single-tile.test.ts delete mode 100644 test/test-get-single-tile.js diff --git a/package-lock.json b/package-lock.json index bcdc6da..3d64f0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1330,6 +1330,7 @@ "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1386,6 +1387,7 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -1745,6 +1747,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2034,6 +2037,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2913,6 +2917,7 @@ "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -3184,7 +3189,8 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/type-check": { "version": "0.4.0", @@ -3253,6 +3259,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3315,6 +3322,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -3390,6 +3398,7 @@ "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", diff --git a/src/geojsonvt.ts b/src/geojsonvt.ts index 7a101b3..9fab066 100644 --- a/src/geojsonvt.ts +++ b/src/geojsonvt.ts @@ -247,7 +247,7 @@ export class GeoJSONVT { /** * Invalidates (removes) tiles affected by the provided features * @internal - * @param features + * @param features */ invalidateTiles(features: GeoJSONVTInternalFeature[]) { const options = this.options; @@ -371,7 +371,7 @@ function toID(z: number, x: number, y: number): number { return (((1 << z) * y + x) * 32) + z; } -export function geoJSONToTile(data: GeoJSON.GeoJSON, z: number, x: number, y: number, options: GeoJSONVTOptions, shouldWrap = false, shouldClip = false) { +export function geoJSONToTile(data: GeoJSON.GeoJSON, z: number, x: number, y: number, options: GeoJSONVTOptions = {}, shouldWrap = false, shouldClip = false) { options = {...defaultTileOptions, ...options}; let features = convert(data, options); diff --git a/test/get-single-tile.test.ts b/test/get-single-tile.test.ts new file mode 100644 index 0000000..ac03b19 --- /dev/null +++ b/test/get-single-tile.test.ts @@ -0,0 +1,38 @@ + +import {test, expect} from 'vitest'; +import fs from 'fs'; + +import {geoJSONToTile} from '../src/geojsonvt'; + +const square = [{ + geometry: [[[4160, -64], [4160, 4160], [-64, 4160], [-64, -64], [4160, -64]]], + type: 3, + tags: {name: 'Pennsylvania', density: 284.3}, + id: '42' +}]; + +test('geoJSONToTile: converts all geometries to a single vector tile', () => { + const geojson = getJSON('single-tile.json'); + const tile = geoJSONToTile(geojson, 12, 1171, 1566); + + expect(tile.features.length).toBe(1); + expect(tile.features[0].tags.name).toBe('P Street Northwest - Massachusetts Avenue Northwest'); +}); + +test('geoJSONToTile: clips geometries outside the tile', () => { + const geojson = getJSON('us-states.json'); + + const tile1 = geoJSONToTile(geojson, 7, 37, 48, {}, false, true); + expect(tile1.features).toEqual(getJSON('us-states-z7-37-48.json')); + + const tile2 = geoJSONToTile(geojson, 9, 148, 192, {}, false, true); + expect(tile2.features).toEqual(square); + + expect(geoJSONToTile(geojson, 11, 800, 400, {}, false, true)).toBeNull(); + expect(geoJSONToTile(geojson, -5, 123.25, 400.25, {}, false, true)).toBeNull(); + expect(geoJSONToTile(geojson, 25, 200, 200, {}, false, true)).toBeNull(); +}); + +function getJSON(name: string) { + return JSON.parse(fs.readFileSync(new URL(`fixtures/${name}`, import.meta.url), 'utf-8')); +} diff --git a/test/test-get-single-tile.js b/test/test-get-single-tile.js deleted file mode 100644 index d433a2a..0000000 --- a/test/test-get-single-tile.js +++ /dev/null @@ -1,41 +0,0 @@ -import test from 'tape'; -import fs from 'fs'; -import path from 'path'; -import {geoJSONToTile} from '../src/index.js'; - -const square = [{ - geometry: [[[4160, -64], [4160, 4160], [-64, 4160], [-64, -64], [4160, -64]]], - type: 3, - tags: {name: 'Pennsylvania', density: 284.3}, - id: '42' -}]; - -test('geoJSONToTile: converts all geometries to a single vector tile', (t) => { - const geojson = getJSON('single-tile.json'); - const tile = geoJSONToTile(geojson, 12, 1171, 1566); - - t.equal(tile.features.length, 1, 'z12-1171-1577'); - t.equal(tile.features[0].tags.name, 'P Street Northwest - Massachusetts Avenue Northwest'); - - t.end(); -}); - -test('geoJSONToTile: clips geometries outside the tile', (t) => { - const geojson = getJSON('us-states.json'); - - const tile1 = geoJSONToTile(geojson, 7, 37, 48, {}, false, true); - t.same(tile1.features, getJSON('us-states-z7-37-48.json'), 'z7-37-48'); - - const tile2 = geoJSONToTile(geojson, 9, 148, 192, {}, false, true); - t.same(tile2.features, square, 'z9-148-192 (clipped square)'); - - t.equal(geoJSONToTile(geojson, 11, 800, 400, {}, false, true), null, 'non-existing tile'); - t.equal(geoJSONToTile(geojson, -5, 123.25, 400.25, {}, false, true), null, 'invalid tile'); - t.equal(geoJSONToTile(geojson, 25, 200, 200, {}, false, true), null, 'invalid tile'); - - t.end(); -}); - -function getJSON(name) { - return JSON.parse(fs.readFileSync(path.join(__dirname, `/fixtures/${ name}`))); -} From a67fc7cef9d2cafa940490c41d164c2d73e699e1 Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 10:52:02 -0800 Subject: [PATCH 04/24] Revert package-lock.json --- package-lock.json | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3d64f0a..bcdc6da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1330,7 +1330,6 @@ "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1387,7 +1386,6 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -1747,7 +1745,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2037,7 +2034,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2917,7 +2913,6 @@ "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -3189,8 +3184,7 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/type-check": { "version": "0.4.0", @@ -3259,7 +3253,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3322,7 +3315,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -3398,7 +3390,6 @@ "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", From 62e3d79eea4a4a267e0b6629e16763a4d759870c Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 10:54:31 -0800 Subject: [PATCH 05/24] Simplify diff --- src/geojsonvt.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/geojsonvt.ts b/src/geojsonvt.ts index 9fab066..a382aa3 100644 --- a/src/geojsonvt.ts +++ b/src/geojsonvt.ts @@ -247,7 +247,7 @@ export class GeoJSONVT { /** * Invalidates (removes) tiles affected by the provided features * @internal - * @param features + * @param features */ invalidateTiles(features: GeoJSONVTInternalFeature[]) { const options = this.options; From f6632c7a9d185033d4ec69c759e7b45ad385ac02 Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 10:55:07 -0800 Subject: [PATCH 06/24] Simplify diff --- src/geojsonvt.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/geojsonvt.ts b/src/geojsonvt.ts index a382aa3..e614154 100644 --- a/src/geojsonvt.ts +++ b/src/geojsonvt.ts @@ -86,7 +86,7 @@ export class GeoJSONVT { * splits features from a parent tile to sub-tiles. * z, x, and y are the coordinates of the parent tile * cz, cx, and cy are the coordinates of the target tile - * + * * If no target tile is specified, splitting stops when we reach the maximum * zoom or the number of points is low as specified in the options. * @internal @@ -247,7 +247,7 @@ export class GeoJSONVT { /** * Invalidates (removes) tiles affected by the provided features * @internal - * @param features + * @param features */ invalidateTiles(features: GeoJSONVTInternalFeature[]) { const options = this.options; From 101b32c25ee084ed4b0aafb78b6235b150b85dd6 Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 10:59:50 -0800 Subject: [PATCH 07/24] Simplify diff --- src/geojsonvt.ts | 2 +- test/get-single-tile.test.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/geojsonvt.ts b/src/geojsonvt.ts index e614154..90ea459 100644 --- a/src/geojsonvt.ts +++ b/src/geojsonvt.ts @@ -247,7 +247,7 @@ export class GeoJSONVT { /** * Invalidates (removes) tiles affected by the provided features * @internal - * @param features + * @param features */ invalidateTiles(features: GeoJSONVTInternalFeature[]) { const options = this.options; diff --git a/test/get-single-tile.test.ts b/test/get-single-tile.test.ts index ac03b19..9cd527a 100644 --- a/test/get-single-tile.test.ts +++ b/test/get-single-tile.test.ts @@ -1,7 +1,6 @@ import {test, expect} from 'vitest'; import fs from 'fs'; - import {geoJSONToTile} from '../src/geojsonvt'; const square = [{ From baba0647852c568d7c1f48885cce4d12e80f9672 Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 11:07:31 -0800 Subject: [PATCH 08/24] Update CHANGELOG.md --- CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb0ab69..ca26e7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,6 @@ ## main ### ✨ Features and improvements -- _...Add new stuff here..._ - -### 🐞 Bug fixes -- _...Add new stuff here..._ +- Add `geoJSONToTile` function to generate a single tile directly from GeoJSON without building the full tile index. (by [montzkie18](https://github.com/montzkie18) and [lucaswoj](https://github.com/lucaswoj)). ## 5.0.4 From 63fd8604713e36223ce19d6e6f9ef897c502ed41 Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 12:22:21 -0800 Subject: [PATCH 09/24] Remove defaultTileOptions --- src/geojsonvt.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/geojsonvt.ts b/src/geojsonvt.ts index 90ea459..446d00a 100644 --- a/src/geojsonvt.ts +++ b/src/geojsonvt.ts @@ -6,16 +6,12 @@ import {createTile, type GeoJSONVTInternalTile} from './tile'; import {applySourceDiff, type GeoJSONVTSourceDiff} from './difference'; import type { GeoJSONVTInternalFeature, GeoJSONVTOptions } from './definitions'; -const defaultTileOptions: GeoJSONVTOptions = { +const defaultOptions: GeoJSONVTOptions = { maxZoom: 14, // max zoom to preserve detail on tolerance: 3, // simplification tolerance (higher means simpler) extent: 4096, // tile extent buffer: 64, // tile buffer on each side - lineMetrics: false // whether to calculate line metrics -}; - -const defaultOptions: GeoJSONVTOptions = { - ...defaultTileOptions, + lineMetrics: false, // whether to calculate line metrics indexMaxZoom: 5, // max zoom in the tile index indexMaxPoints: 100000, // max number of points per tile in the tile index promoteId: null, // name of a feature property to be promoted to feature.id @@ -372,7 +368,7 @@ function toID(z: number, x: number, y: number): number { } export function geoJSONToTile(data: GeoJSON.GeoJSON, z: number, x: number, y: number, options: GeoJSONVTOptions = {}, shouldWrap = false, shouldClip = false) { - options = {...defaultTileOptions, ...options}; + options = {...defaultOptions, ...options}; let features = convert(data, options); if (shouldWrap) { From ab959046a1e9777618ac098e0c1dd483750597a0 Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 12:23:33 -0800 Subject: [PATCH 10/24] Remove code comments --- src/geojsonvt.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/geojsonvt.ts b/src/geojsonvt.ts index 446d00a..cb3ad59 100644 --- a/src/geojsonvt.ts +++ b/src/geojsonvt.ts @@ -7,16 +7,16 @@ import {applySourceDiff, type GeoJSONVTSourceDiff} from './difference'; import type { GeoJSONVTInternalFeature, GeoJSONVTOptions } from './definitions'; const defaultOptions: GeoJSONVTOptions = { - maxZoom: 14, // max zoom to preserve detail on - tolerance: 3, // simplification tolerance (higher means simpler) - extent: 4096, // tile extent - buffer: 64, // tile buffer on each side - lineMetrics: false, // whether to calculate line metrics - indexMaxZoom: 5, // max zoom in the tile index - indexMaxPoints: 100000, // max number of points per tile in the tile index - promoteId: null, // name of a feature property to be promoted to feature.id - generateId: false, // whether to generate feature ids. Cannot be used with promoteId - debug: 0 // logging level (0, 1 or 2) + maxZoom: 14, + tolerance: 3, + extent: 4096, + buffer: 64, + lineMetrics: false, + indexMaxZoom: 5, + indexMaxPoints: 100000, + promoteId: null, + generateId: false, + debug: 0 } /** From f283e9f64a55ae0a76fab9b6c09e4e5d4098e7ea Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 12:32:53 -0800 Subject: [PATCH 11/24] Create dedicated GeoJSONToTileOptions type --- src/definitions.ts | 14 +++++++++++++- src/geojsonvt.ts | 5 +++-- src/index.ts | 9 +++++---- test/get-single-tile.test.ts | 10 +++++----- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/definitions.ts b/src/definitions.ts index 538685c..e9f9b77 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -55,6 +55,18 @@ export type GeoJSONVTOptions = { debug?: number; }; +export type GeoJSONToTileOptions = GeoJSONVTOptions & { + /** + * Whether to wrap features around the antimeridian + * @default false + */ + wrap?: boolean; + /** + * Whether to clip features to the tile boundary + * @default false + */ + clip?: boolean; +}; export type StartEndSizeArray = number[] & { start?: number; end?: number; size?: number }; @@ -103,4 +115,4 @@ export type GeoJSONVTInternalFeature = | GeoJSONVTInternalLineStringFeature | GeoJSONVTInternalMultiLineStringFeature | GeoJSONVTInternalPolygonFeature - | GeoJSONVTInternalMultiPolygonFeature; \ No newline at end of file + | GeoJSONVTInternalMultiPolygonFeature; diff --git a/src/geojsonvt.ts b/src/geojsonvt.ts index cb3ad59..2b4270c 100644 --- a/src/geojsonvt.ts +++ b/src/geojsonvt.ts @@ -4,7 +4,7 @@ import {wrap} from './wrap'; import {transformTile, type GeoJSONVTTile} from './transform'; import {createTile, type GeoJSONVTInternalTile} from './tile'; import {applySourceDiff, type GeoJSONVTSourceDiff} from './difference'; -import type { GeoJSONVTInternalFeature, GeoJSONVTOptions } from './definitions'; +import type { GeoJSONVTInternalFeature, GeoJSONVTOptions, GeoJSONToTileOptions } from './definitions'; const defaultOptions: GeoJSONVTOptions = { maxZoom: 14, @@ -367,8 +367,9 @@ function toID(z: number, x: number, y: number): number { return (((1 << z) * y + x) * 32) + z; } -export function geoJSONToTile(data: GeoJSON.GeoJSON, z: number, x: number, y: number, options: GeoJSONVTOptions = {}, shouldWrap = false, shouldClip = false) { +export function geoJSONToTile(data: GeoJSON.GeoJSON, z: number, x: number, y: number, options: GeoJSONToTileOptions = {}) { options = {...defaultOptions, ...options}; + const {wrap: shouldWrap = false, clip: shouldClip = false} = options; let features = convert(data, options); if (shouldWrap) { diff --git a/src/index.ts b/src/index.ts index fb45991..29478b7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,16 +2,17 @@ import type {GeoJSONVTFeature, GeoJSONVTFeatureNonPoint, GeoJSONVTFeaturePoint, GeoJSONVTTile} from './transform'; import type {GeoJSONVTInternalTile, GeoJSONVTInternalTileFeature, GeoJSONVTInternalTileFeaturePoint, GeoJSONVTInternalTileFeatureNonPoint} from './tile'; import type {GeoJSONVTFeatureDiff, GeoJSONVTSourceDiff} from './difference'; -import type {GeoJSONVTInternalFeature, GeoJSONVTInternalLineStringFeature, GeoJSONVTInternalMultiLineStringFeature, GeoJSONVTInternalMultiPointFeature, GeoJSONVTInternalMultiPolygonFeature, GeoJSONVTInternalPointFeature, GeoJSONVTInternalPolygonFeature, GeoJSONVTOptions, PartialGeoJSONVTFeature, StartEndSizeArray} from './definitions'; +import type {GeoJSONVTInternalFeature, GeoJSONVTInternalLineStringFeature, GeoJSONVTInternalMultiLineStringFeature, GeoJSONVTInternalMultiPointFeature, GeoJSONVTInternalMultiPolygonFeature, GeoJSONVTInternalPointFeature, GeoJSONVTInternalPolygonFeature, GeoJSONVTOptions, GeoJSONToTileOptions, PartialGeoJSONVTFeature, StartEndSizeArray} from './definitions'; import {GeoJSONVT} from './geojsonvt'; export default function geojsonvt(data: GeoJSON.GeoJSON, options?: GeoJSONVTOptions) { return new GeoJSONVT(data, options); } -export type { - GeoJSONVTInternalFeature, - GeoJSONVTOptions, +export type { + GeoJSONVTInternalFeature, + GeoJSONVTOptions, + GeoJSONToTileOptions, GeoJSONVTInternalTile, GeoJSONVTInternalTileFeature, PartialGeoJSONVTFeature, diff --git a/test/get-single-tile.test.ts b/test/get-single-tile.test.ts index 9cd527a..fd2f1bd 100644 --- a/test/get-single-tile.test.ts +++ b/test/get-single-tile.test.ts @@ -21,15 +21,15 @@ test('geoJSONToTile: converts all geometries to a single vector tile', () => { test('geoJSONToTile: clips geometries outside the tile', () => { const geojson = getJSON('us-states.json'); - const tile1 = geoJSONToTile(geojson, 7, 37, 48, {}, false, true); + const tile1 = geoJSONToTile(geojson, 7, 37, 48, {clip: true}); expect(tile1.features).toEqual(getJSON('us-states-z7-37-48.json')); - const tile2 = geoJSONToTile(geojson, 9, 148, 192, {}, false, true); + const tile2 = geoJSONToTile(geojson, 9, 148, 192, {clip: true}); expect(tile2.features).toEqual(square); - expect(geoJSONToTile(geojson, 11, 800, 400, {}, false, true)).toBeNull(); - expect(geoJSONToTile(geojson, -5, 123.25, 400.25, {}, false, true)).toBeNull(); - expect(geoJSONToTile(geojson, 25, 200, 200, {}, false, true)).toBeNull(); + expect(geoJSONToTile(geojson, 11, 800, 400, {clip: true})).toBeNull(); + expect(geoJSONToTile(geojson, -5, 123.25, 400.25, {clip: true})).toBeNull(); + expect(geoJSONToTile(geojson, 25, 200, 200, {clip: true})).toBeNull(); }); function getJSON(name: string) { From 14f50faf550a2600197133d6cedb04842dc3274c Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 12:34:07 -0800 Subject: [PATCH 12/24] Export geoJSONToTile from index.ts --- src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.ts b/src/index.ts index 29478b7..9156712 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,8 @@ export default function geojsonvt(data: GeoJSON.GeoJSON, options?: GeoJSONVTOpti return new GeoJSONVT(data, options); } +export {geoJSONToTile} from './geojsonvt'; + export type { GeoJSONVTInternalFeature, GeoJSONVTOptions, From c4e2bf37474e7a8f6164f314e6a096d8929b31ef Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 12:37:38 -0800 Subject: [PATCH 13/24] Add TSDoc comments to geoJSONToTile function --- src/geojsonvt.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/geojsonvt.ts b/src/geojsonvt.ts index 2b4270c..e437ef3 100644 --- a/src/geojsonvt.ts +++ b/src/geojsonvt.ts @@ -367,6 +367,38 @@ function toID(z: number, x: number, y: number): number { return (((1 << z) * y + x) * 32) + z; } +/** + * Converts GeoJSON data directly to a single vector tile without building a tile index. + * + * Unlike the {@link GeoJSONVT} class which builds a hierarchical tile index for efficient + * repeated tile access, this function generates a single tile on-demand. This is useful when: + * - You only need one specific tile and don't need to query multiple tiles + * - The source data is already spatially filtered to the tile's bounding box + * - You want to avoid the overhead of building a full tile index + * + * @example + * ```ts + * import {geoJSONToTile} from '@maplibre/geojson-vt'; + * + * const geojson = { + * type: 'FeatureCollection', + * features: [{ + * type: 'Feature', + * geometry: { type: 'Point', coordinates: [-77.03, 38.90] }, + * properties: { name: 'Washington, D.C.' } + * }] + * }; + * + * const tile = geoJSONToTile(geojson, 10, 292, 391, { extent: 4096 }); + * ``` + * + * @param data - GeoJSON data (Feature, FeatureCollection, or Geometry) + * @param z - Tile zoom level + * @param x - Tile x coordinate + * @param y - Tile y coordinate + * @param options - Optional configuration for tile generation + * @returns The generated tile with geometries in tile coordinates, or null if no features + */ export function geoJSONToTile(data: GeoJSON.GeoJSON, z: number, x: number, y: number, options: GeoJSONToTileOptions = {}) { options = {...defaultOptions, ...options}; const {wrap: shouldWrap = false, clip: shouldClip = false} = options; From c55c1fcca094985c74c76c2a62747e29e00b3c90 Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 12:38:44 -0800 Subject: [PATCH 14/24] Fix CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca26e7f..c25ea76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,13 @@ ## main + ### ✨ Features and improvements - Add `geoJSONToTile` function to generate a single tile directly from GeoJSON without building the full tile index. (by [montzkie18](https://github.com/montzkie18) and [lucaswoj](https://github.com/lucaswoj)). +### 🐞 Bug fixes +- _...Add new stuff here..._ + +### ✨ Features and improvements + ## 5.0.4 ### ✨ Features and improvements From 6b6c3c4ce26e18020e45ce62a8ef549ab01d1d3f Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 12:47:25 -0800 Subject: [PATCH 15/24] Fix CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c25ea76..bdffef8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### ✨ Features and improvements - Add `geoJSONToTile` function to generate a single tile directly from GeoJSON without building the full tile index. (by [montzkie18](https://github.com/montzkie18) and [lucaswoj](https://github.com/lucaswoj)). +- _...Add new stuff here..._ ### 🐞 Bug fixes - _...Add new stuff here..._ From 6a493fa674b9fb134662d6a784b9b9888d9e18a1 Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 12:50:22 -0800 Subject: [PATCH 16/24] Fix CHANGELOG.md --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdffef8..a78995f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,6 @@ ### 🐞 Bug fixes - _...Add new stuff here..._ -### ✨ Features and improvements - ## 5.0.4 ### ✨ Features and improvements From 9f7a2490f1965fc35b5faac242a7a37e88813071 Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 12:50:38 -0800 Subject: [PATCH 17/24] Fix CHANGELOG.md --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a78995f..cbcc864 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,4 @@ ## main - ### ✨ Features and improvements - Add `geoJSONToTile` function to generate a single tile directly from GeoJSON without building the full tile index. (by [montzkie18](https://github.com/montzkie18) and [lucaswoj](https://github.com/lucaswoj)). - _...Add new stuff here..._ From 454a431b92aa082e9aa8bd53091515e666299b4f Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 12:51:01 -0800 Subject: [PATCH 18/24] Simplify diff --- src/definitions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/definitions.ts b/src/definitions.ts index e9f9b77..c0383a4 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -115,4 +115,4 @@ export type GeoJSONVTInternalFeature = | GeoJSONVTInternalLineStringFeature | GeoJSONVTInternalMultiLineStringFeature | GeoJSONVTInternalPolygonFeature - | GeoJSONVTInternalMultiPolygonFeature; + | GeoJSONVTInternalMultiPolygonFeature; \ No newline at end of file From eb5c66d1c86a7aebaa936f7c0da62d33ba64f3e2 Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 12:52:04 -0800 Subject: [PATCH 19/24] Revert changes to defaultOptions --- src/geojsonvt.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/geojsonvt.ts b/src/geojsonvt.ts index e437ef3..d3cf212 100644 --- a/src/geojsonvt.ts +++ b/src/geojsonvt.ts @@ -8,16 +8,17 @@ import type { GeoJSONVTInternalFeature, GeoJSONVTOptions, GeoJSONToTileOptions } const defaultOptions: GeoJSONVTOptions = { maxZoom: 14, + indexMaxZoom: 5, + indexMaxPoints: 100000, tolerance: 3, extent: 4096, buffer: 64, lineMetrics: false, - indexMaxZoom: 5, - indexMaxPoints: 100000, promoteId: null, generateId: false, + updateable: false, debug: 0 -} +}; /** * Main class for creating and managing a vector tile index from GeoJSON data. From c96b8a33cdf66609e2357d3958c3638a63843dc3 Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 12:54:10 -0800 Subject: [PATCH 20/24] Move geoJSONToTile to a new file --- src/geoJSONToTile.ts | 60 ++++++++++++++++++++++++++++++++++++ src/geojsonvt.ts | 58 ++-------------------------------- src/index.ts | 2 +- test/get-single-tile.test.ts | 2 +- 4 files changed, 65 insertions(+), 57 deletions(-) create mode 100644 src/geoJSONToTile.ts diff --git a/src/geoJSONToTile.ts b/src/geoJSONToTile.ts new file mode 100644 index 0000000..5c3b6bb --- /dev/null +++ b/src/geoJSONToTile.ts @@ -0,0 +1,60 @@ +import {clip} from './clip'; +import {convert} from './convert'; +import type {GeoJSONToTileOptions} from './definitions'; +import {defaultOptions} from './geojsonvt'; +import {createTile} from './tile'; +import {transformTile} from './transform'; +import {wrap} from './wrap'; + +/** + * Converts GeoJSON data directly to a single vector tile without building a tile index. + * + * Unlike the {@link GeoJSONVT} class which builds a hierarchical tile index for efficient + * repeated tile access, this function generates a single tile on-demand. This is useful when: + * - You only need one specific tile and don't need to query multiple tiles + * - The source data is already spatially filtered to the tile's bounding box + * - You want to avoid the overhead of building a full tile index + * + * @example + * ```ts + * import {geoJSONToTile} from '@maplibre/geojson-vt'; + * + * const geojson = { + * type: 'FeatureCollection', + * features: [{ + * type: 'Feature', + * geometry: { type: 'Point', coordinates: [-77.03, 38.90] }, + * properties: { name: 'Washington, D.C.' } + * }] + * }; + * + * const tile = geoJSONToTile(geojson, 10, 292, 391, { extent: 4096 }); + * ``` + * + * @param data - GeoJSON data (Feature, FeatureCollection, or Geometry) + * @param z - Tile zoom level + * @param x - Tile x coordinate + * @param y - Tile y coordinate + * @param options - Optional configuration for tile generation + * @returns The generated tile with geometries in tile coordinates, or null if no features + */ + +export function geoJSONToTile(data: GeoJSON.GeoJSON, z: number, x: number, y: number, options: GeoJSONToTileOptions = {}) { + options = {...defaultOptions, ...options}; + const {wrap: shouldWrap = false, clip: shouldClip = false} = options; + + let features = convert(data, options); + if (shouldWrap) { + features = wrap(features, options); + } + if (shouldClip || options.lineMetrics) { + const pow2 = 1 << z; + const buffer = options.buffer / options.extent; + const left = clip(features, pow2, (x - buffer), (x + 1 + buffer), 0, -1, 2, options); + features = clip(left || [], pow2, (y - buffer), (y + 1 + buffer), 1, -1, 2, options); + } + + return features + ? transformTile(createTile(features, z, x, y, options), options.extent) + : null; +} diff --git a/src/geojsonvt.ts b/src/geojsonvt.ts index d3cf212..91722e5 100644 --- a/src/geojsonvt.ts +++ b/src/geojsonvt.ts @@ -4,9 +4,9 @@ import {wrap} from './wrap'; import {transformTile, type GeoJSONVTTile} from './transform'; import {createTile, type GeoJSONVTInternalTile} from './tile'; import {applySourceDiff, type GeoJSONVTSourceDiff} from './difference'; -import type { GeoJSONVTInternalFeature, GeoJSONVTOptions, GeoJSONToTileOptions } from './definitions'; +import type { GeoJSONVTInternalFeature, GeoJSONVTOptions } from './definitions'; -const defaultOptions: GeoJSONVTOptions = { +export const defaultOptions: GeoJSONVTOptions = { maxZoom: 14, indexMaxZoom: 5, indexMaxPoints: 100000, @@ -366,56 +366,4 @@ export class GeoJSONVT { function toID(z: number, x: number, y: number): number { return (((1 << z) * y + x) * 32) + z; -} - -/** - * Converts GeoJSON data directly to a single vector tile without building a tile index. - * - * Unlike the {@link GeoJSONVT} class which builds a hierarchical tile index for efficient - * repeated tile access, this function generates a single tile on-demand. This is useful when: - * - You only need one specific tile and don't need to query multiple tiles - * - The source data is already spatially filtered to the tile's bounding box - * - You want to avoid the overhead of building a full tile index - * - * @example - * ```ts - * import {geoJSONToTile} from '@maplibre/geojson-vt'; - * - * const geojson = { - * type: 'FeatureCollection', - * features: [{ - * type: 'Feature', - * geometry: { type: 'Point', coordinates: [-77.03, 38.90] }, - * properties: { name: 'Washington, D.C.' } - * }] - * }; - * - * const tile = geoJSONToTile(geojson, 10, 292, 391, { extent: 4096 }); - * ``` - * - * @param data - GeoJSON data (Feature, FeatureCollection, or Geometry) - * @param z - Tile zoom level - * @param x - Tile x coordinate - * @param y - Tile y coordinate - * @param options - Optional configuration for tile generation - * @returns The generated tile with geometries in tile coordinates, or null if no features - */ -export function geoJSONToTile(data: GeoJSON.GeoJSON, z: number, x: number, y: number, options: GeoJSONToTileOptions = {}) { - options = {...defaultOptions, ...options}; - const {wrap: shouldWrap = false, clip: shouldClip = false} = options; - - let features = convert(data, options); - if (shouldWrap) { - features = wrap(features, options); - } - if (shouldClip || options.lineMetrics) { - const pow2 = 1 << z; - const buffer = options.buffer / options.extent; - const left = clip(features, pow2, (x - buffer), (x + 1 + buffer), 0, -1, 2, options); - features = clip(left || [], pow2, (y - buffer), (y + 1 + buffer), 1, -1, 2, options); - } - - return features - ? transformTile(createTile(features, z, x, y, options), options.extent) - : null; -} +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 9156712..d74d16c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,7 @@ export default function geojsonvt(data: GeoJSON.GeoJSON, options?: GeoJSONVTOpti return new GeoJSONVT(data, options); } -export {geoJSONToTile} from './geojsonvt'; +export {geoJSONToTile} from './geoJSONToTile'; export type { GeoJSONVTInternalFeature, diff --git a/test/get-single-tile.test.ts b/test/get-single-tile.test.ts index fd2f1bd..f319ff4 100644 --- a/test/get-single-tile.test.ts +++ b/test/get-single-tile.test.ts @@ -1,7 +1,7 @@ import {test, expect} from 'vitest'; import fs from 'fs'; -import {geoJSONToTile} from '../src/geojsonvt'; +import {geoJSONToTile} from '../src/geoJSONToTile'; const square = [{ geometry: [[[4160, -64], [4160, 4160], [-64, 4160], [-64, -64], [4160, -64]]], From bb7048f26366934d5b26bbb34d77c55255028449 Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 13:06:25 -0800 Subject: [PATCH 21/24] Update tests --- test/geoJSONToTile.test.ts | 77 ++++++++++++++++++++++++++++++++++++ test/get-single-tile.test.ts | 37 ----------------- 2 files changed, 77 insertions(+), 37 deletions(-) create mode 100644 test/geoJSONToTile.test.ts delete mode 100644 test/get-single-tile.test.ts diff --git a/test/geoJSONToTile.test.ts b/test/geoJSONToTile.test.ts new file mode 100644 index 0000000..d8a36cb --- /dev/null +++ b/test/geoJSONToTile.test.ts @@ -0,0 +1,77 @@ + +import {test, expect} from 'vitest'; +import fs from 'fs'; +import {geoJSONToTile} from '../src/geoJSONToTile'; + +const square = [{ + geometry: [[[4160, -64], [4160, 4160], [-64, 4160], [-64, -64], [4160, -64]]], + type: 3, + tags: {name: 'Pennsylvania', density: 284.3}, + id: '42' +}]; + +test('geoJSONToTile: converts all geometries to a single vector tile', () => { + const geojson = getJSON('single-tile.json'); + const tile = geoJSONToTile(geojson, 12, 1171, 1566); + + expect(tile.features.length).toBe(1); + expect(tile.features[0].tags.name).toBe('P Street Northwest - Massachusetts Avenue Northwest'); +}); + +test('geoJSONToTile: wraps features across the antimeridian', () => { + const geojson: GeoJSON.FeatureCollection = { + type: 'FeatureCollection', + features: [{ + type: 'Feature', + properties: {name: 'test'}, + geometry: { + type: 'LineString', + coordinates: [[-200, 0], [200, 0]] + } + }] + }; + + const tileWithoutWrap = geoJSONToTile(geojson, 0, 0, 0, {wrap: false, clip: true}); + expect(tileWithoutWrap.features.length).toBe(1); + + const tileWithWrap = geoJSONToTile(geojson, 0, 0, 0, {wrap: true, clip: true}); + expect(tileWithWrap.features.length).toBe(3); +}); + +test('geoJSONToTile: shouldWrap duplicates features that extend beyond world bounds', () => { + const geojson: GeoJSON.FeatureCollection = { + type: 'FeatureCollection', + features: [{ + type: 'Feature', + properties: {name: 'beyond-bounds'}, + geometry: { + type: 'LineString', + coordinates: [[-200, 0], [-170, 0]] + } + }] + }; + + const tileWithoutWrap = geoJSONToTile(geojson, 0, 0, 0, {wrap: false, clip: false}); + expect(tileWithoutWrap.features.length).toBe(1); + + const tileWithWrap = geoJSONToTile(geojson, 0, 0, 0, {wrap: true, clip: false}); + expect(tileWithWrap.features.length).toBe(2); +}); + +test('geoJSONToTile: clips geometries outside the tile', () => { + const geojson = getJSON('us-states.json'); + + const tile1 = geoJSONToTile(geojson, 7, 37, 48, {clip: true}); + expect(tile1.features).toEqual(getJSON('us-states-z7-37-48.json')); + + const tile2 = geoJSONToTile(geojson, 9, 148, 192, {clip: true}); + expect(tile2.features).toEqual(square); + + expect(geoJSONToTile(geojson, 11, 800, 400, {clip: true})).toBeNull(); + expect(geoJSONToTile(geojson, -5, 123.25, 400.25, {clip: true})).toBeNull(); + expect(geoJSONToTile(geojson, 25, 200, 200, {clip: true})).toBeNull(); +}); + +function getJSON(name: string) { + return JSON.parse(fs.readFileSync(new URL(`fixtures/${name}`, import.meta.url), 'utf-8')); +} diff --git a/test/get-single-tile.test.ts b/test/get-single-tile.test.ts deleted file mode 100644 index f319ff4..0000000 --- a/test/get-single-tile.test.ts +++ /dev/null @@ -1,37 +0,0 @@ - -import {test, expect} from 'vitest'; -import fs from 'fs'; -import {geoJSONToTile} from '../src/geoJSONToTile'; - -const square = [{ - geometry: [[[4160, -64], [4160, 4160], [-64, 4160], [-64, -64], [4160, -64]]], - type: 3, - tags: {name: 'Pennsylvania', density: 284.3}, - id: '42' -}]; - -test('geoJSONToTile: converts all geometries to a single vector tile', () => { - const geojson = getJSON('single-tile.json'); - const tile = geoJSONToTile(geojson, 12, 1171, 1566); - - expect(tile.features.length).toBe(1); - expect(tile.features[0].tags.name).toBe('P Street Northwest - Massachusetts Avenue Northwest'); -}); - -test('geoJSONToTile: clips geometries outside the tile', () => { - const geojson = getJSON('us-states.json'); - - const tile1 = geoJSONToTile(geojson, 7, 37, 48, {clip: true}); - expect(tile1.features).toEqual(getJSON('us-states-z7-37-48.json')); - - const tile2 = geoJSONToTile(geojson, 9, 148, 192, {clip: true}); - expect(tile2.features).toEqual(square); - - expect(geoJSONToTile(geojson, 11, 800, 400, {clip: true})).toBeNull(); - expect(geoJSONToTile(geojson, -5, 123.25, 400.25, {clip: true})).toBeNull(); - expect(geoJSONToTile(geojson, 25, 200, 200, {clip: true})).toBeNull(); -}); - -function getJSON(name: string) { - return JSON.parse(fs.readFileSync(new URL(`fixtures/${name}`, import.meta.url), 'utf-8')); -} From cd5b765741e4e419d5eb7b205848d81736a5ed7e Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 29 Jan 2026 13:21:12 -0800 Subject: [PATCH 22/24] Improve tests --- test/geoJSONToTile.test.ts | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/test/geoJSONToTile.test.ts b/test/geoJSONToTile.test.ts index d8a36cb..fba0d80 100644 --- a/test/geoJSONToTile.test.ts +++ b/test/geoJSONToTile.test.ts @@ -18,7 +18,7 @@ test('geoJSONToTile: converts all geometries to a single vector tile', () => { expect(tile.features[0].tags.name).toBe('P Street Northwest - Massachusetts Avenue Northwest'); }); -test('geoJSONToTile: wraps features across the antimeridian', () => { +test('geoJSONToTile: wrap features across the antimeridian', () => { const geojson: GeoJSON.FeatureCollection = { type: 'FeatureCollection', features: [{ @@ -32,10 +32,18 @@ test('geoJSONToTile: wraps features across the antimeridian', () => { }; const tileWithoutWrap = geoJSONToTile(geojson, 0, 0, 0, {wrap: false, clip: true}); - expect(tileWithoutWrap.features.length).toBe(1); + expect(tileWithoutWrap.features).toEqual([{ + type: 2, + tags: {name: 'test'}, + geometry: [[[-64, 2048], [4160, 2048]]] + }]); const tileWithWrap = geoJSONToTile(geojson, 0, 0, 0, {wrap: true, clip: true}); - expect(tileWithWrap.features.length).toBe(3); + expect(tileWithWrap.features).toEqual([ + {type: 2, tags: {name: 'test'}, geometry: [[[3868, 2048], [4160, 2048]]]}, + {type: 2, tags: {name: 'test'}, geometry: [[[-64, 2048], [4160, 2048]]]}, + {type: 2, tags: {name: 'test'}, geometry: [[[-64, 2048], [228, 2048]]]} + ]); }); test('geoJSONToTile: shouldWrap duplicates features that extend beyond world bounds', () => { @@ -43,7 +51,7 @@ test('geoJSONToTile: shouldWrap duplicates features that extend beyond world bou type: 'FeatureCollection', features: [{ type: 'Feature', - properties: {name: 'beyond-bounds'}, + properties: {name: 'test'}, geometry: { type: 'LineString', coordinates: [[-200, 0], [-170, 0]] @@ -52,10 +60,17 @@ test('geoJSONToTile: shouldWrap duplicates features that extend beyond world bou }; const tileWithoutWrap = geoJSONToTile(geojson, 0, 0, 0, {wrap: false, clip: false}); - expect(tileWithoutWrap.features.length).toBe(1); + expect(tileWithoutWrap.features).toEqual([{ + type: 2, + tags: {name: 'test'}, + geometry: [[[-228, 2048], [114, 2048]]] + }]); const tileWithWrap = geoJSONToTile(geojson, 0, 0, 0, {wrap: true, clip: false}); - expect(tileWithWrap.features.length).toBe(2); + expect(tileWithWrap.features).toEqual([ + {type: 2, tags: {name: 'test'}, geometry: [[[3868, 2048], [4160, 2048]]]}, + {type: 2, tags: {name: 'test'}, geometry: [[[-64, 2048], [114, 2048]]]} + ]); }); test('geoJSONToTile: clips geometries outside the tile', () => { From ff64c23cf597a4070008eb264623fa17218d3990 Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Fri, 30 Jan 2026 07:21:44 -0800 Subject: [PATCH 23/24] Add return type to 'geoJSONToTile' --- src/geoJSONToTile.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/geoJSONToTile.ts b/src/geoJSONToTile.ts index 5c3b6bb..d835f4f 100644 --- a/src/geoJSONToTile.ts +++ b/src/geoJSONToTile.ts @@ -3,7 +3,7 @@ import {convert} from './convert'; import type {GeoJSONToTileOptions} from './definitions'; import {defaultOptions} from './geojsonvt'; import {createTile} from './tile'; -import {transformTile} from './transform'; +import {transformTile, type GeoJSONVTTile} from './transform'; import {wrap} from './wrap'; /** @@ -39,7 +39,7 @@ import {wrap} from './wrap'; * @returns The generated tile with geometries in tile coordinates, or null if no features */ -export function geoJSONToTile(data: GeoJSON.GeoJSON, z: number, x: number, y: number, options: GeoJSONToTileOptions = {}) { +export function geoJSONToTile(data: GeoJSON.GeoJSON, z: number, x: number, y: number, options: GeoJSONToTileOptions = {}): GeoJSONVTTile | null { options = {...defaultOptions, ...options}; const {wrap: shouldWrap = false, clip: shouldClip = false} = options; From 57ecfc5c7cb6fdcf2dedd797fe5488c76c126f62 Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Fri, 30 Jan 2026 07:28:43 -0800 Subject: [PATCH 24/24] Return an empty tile instead of null --- src/geoJSONToTile.ts | 6 ++---- test/geoJSONToTile.test.ts | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/geoJSONToTile.ts b/src/geoJSONToTile.ts index d835f4f..4552c37 100644 --- a/src/geoJSONToTile.ts +++ b/src/geoJSONToTile.ts @@ -39,7 +39,7 @@ import {wrap} from './wrap'; * @returns The generated tile with geometries in tile coordinates, or null if no features */ -export function geoJSONToTile(data: GeoJSON.GeoJSON, z: number, x: number, y: number, options: GeoJSONToTileOptions = {}): GeoJSONVTTile | null { +export function geoJSONToTile(data: GeoJSON.GeoJSON, z: number, x: number, y: number, options: GeoJSONToTileOptions = {}): GeoJSONVTTile { options = {...defaultOptions, ...options}; const {wrap: shouldWrap = false, clip: shouldClip = false} = options; @@ -54,7 +54,5 @@ export function geoJSONToTile(data: GeoJSON.GeoJSON, z: number, x: number, y: nu features = clip(left || [], pow2, (y - buffer), (y + 1 + buffer), 1, -1, 2, options); } - return features - ? transformTile(createTile(features, z, x, y, options), options.extent) - : null; + return transformTile(createTile(features ?? [], z, x, y, options), options.extent); } diff --git a/test/geoJSONToTile.test.ts b/test/geoJSONToTile.test.ts index fba0d80..a48017e 100644 --- a/test/geoJSONToTile.test.ts +++ b/test/geoJSONToTile.test.ts @@ -82,9 +82,9 @@ test('geoJSONToTile: clips geometries outside the tile', () => { const tile2 = geoJSONToTile(geojson, 9, 148, 192, {clip: true}); expect(tile2.features).toEqual(square); - expect(geoJSONToTile(geojson, 11, 800, 400, {clip: true})).toBeNull(); - expect(geoJSONToTile(geojson, -5, 123.25, 400.25, {clip: true})).toBeNull(); - expect(geoJSONToTile(geojson, 25, 200, 200, {clip: true})).toBeNull(); + expect(geoJSONToTile(geojson, 11, 800, 400, {clip: true}).features).toEqual([]); + expect(geoJSONToTile(geojson, -5, 123.25, 400.25, {clip: true}).features).toEqual([]); + expect(geoJSONToTile(geojson, 25, 200, 200, {clip: true}).features).toEqual([]); }); function getJSON(name: string) {