From 5417b5c8b601a07a86dd0e0c00f625bc98bfe4a6 Mon Sep 17 00:00:00 2001 From: Maciej Ziarkowski Date: Mon, 7 Oct 2019 13:34:22 +0100 Subject: [PATCH] Use functions instead of classes in tile rendering --- app/src/tiles/dataDefinition.ts | 2 +- app/src/tiles/rendererDefinition.ts | 74 ++++++------ app/src/tiles/renderers/blankRenderer.ts | 21 ---- app/src/tiles/renderers/branchingRenderer.ts | 23 ---- app/src/tiles/renderers/cachedRenderer.ts | 32 ------ app/src/tiles/renderers/createBlankTile.ts | 18 +++ app/src/tiles/renderers/datasourceRenderer.ts | 105 ------------------ app/src/tiles/renderers/getTileWithCaching.ts | 21 ++++ .../tiles/renderers/renderDataSourceTile.ts | 64 +++++++++++ app/src/tiles/renderers/stitchRenderer.ts | 67 ----------- app/src/tiles/renderers/stitchTile.ts | 54 +++++++++ app/src/tiles/renderers/windowedRenderer.ts | 34 ------ app/src/tiles/tileserver.ts | 4 +- app/src/tiles/types.ts | 19 +++- app/src/tiles/util.ts | 10 +- 15 files changed, 219 insertions(+), 329 deletions(-) delete mode 100644 app/src/tiles/renderers/blankRenderer.ts delete mode 100644 app/src/tiles/renderers/branchingRenderer.ts delete mode 100644 app/src/tiles/renderers/cachedRenderer.ts create mode 100644 app/src/tiles/renderers/createBlankTile.ts delete mode 100644 app/src/tiles/renderers/datasourceRenderer.ts create mode 100644 app/src/tiles/renderers/getTileWithCaching.ts create mode 100644 app/src/tiles/renderers/renderDataSourceTile.ts delete mode 100644 app/src/tiles/renderers/stitchRenderer.ts create mode 100644 app/src/tiles/renderers/stitchTile.ts delete mode 100644 app/src/tiles/renderers/windowedRenderer.ts diff --git a/app/src/tiles/dataDefinition.ts b/app/src/tiles/dataDefinition.ts index 0d56cf1a..55bcf55b 100644 --- a/app/src/tiles/dataDefinition.ts +++ b/app/src/tiles/dataDefinition.ts @@ -1,5 +1,5 @@ import { strictParseInt } from "../parse"; -import { DataConfig } from "./renderers/datasourceRenderer"; +import { DataConfig } from "./types"; const BUILDING_LAYER_DEFINITIONS = { base_light: `( diff --git a/app/src/tiles/rendererDefinition.ts b/app/src/tiles/rendererDefinition.ts index 16b6865e..635abae5 100644 --- a/app/src/tiles/rendererDefinition.ts +++ b/app/src/tiles/rendererDefinition.ts @@ -1,33 +1,28 @@ import { TileCache } from "./tileCache"; -import { BoundingBox, TileParams } from "./types"; -import { StitchRenderer } from "./renderers/stitchRenderer"; -import { CachedRenderer } from "./renderers/cachedRenderer"; -import { BranchingRenderer } from "./renderers/branchingRenderer"; -import { WindowedRenderer } from "./renderers/windowedRenderer"; -import { BlankRenderer } from "./renderers/blankRenderer"; -import { DatasourceRenderer } from "./renderers/datasourceRenderer"; +import { BoundingBox, TileParams, Tile } from "./types"; import { getBuildingsDataConfig, getHighlightDataConfig, BUILDING_LAYER_DEFINITIONS } from "./dataDefinition"; +import { isOutsideExtent } from "./util"; +import { renderDataSourceTile } from "./renderers/renderDataSourceTile"; +import { getTileWithCaching } from "./renderers/getTileWithCaching"; +import { stitchTile } from "./renderers/stitchTile"; +import { createBlankTile } from "./renderers/createBlankTile"; /** * A list of all tilesets handled by the tile server */ const allTilesets = ['highlight', ...Object.keys(BUILDING_LAYER_DEFINITIONS)]; -const buildingDataRenderer = new DatasourceRenderer(getBuildingsDataConfig); - -const stitchRenderer = new StitchRenderer(undefined); // depends recurrently on cache, so parameter will be set later - /** * Zoom level when we switch from rendering direct from database to instead composing tiles * from the zoom level below - gets similar effect, with much lower load on Postgres */ const STITCH_THRESHOLD = 12; -const renderOrStitchRenderer = new BranchingRenderer( - ({ z }) => z <= STITCH_THRESHOLD, - stitchRenderer, // refer to the prepared stitch renderer - buildingDataRenderer -); +/** + * Hard-code extent so we can short-circuit rendering and return empty/transparent tiles outside the area of interest + * bbox in CRS epsg:3857 in form: [w, s, e, n] + */ +const EXTENT_BBOX: BoundingBox = [-61149.622628, 6667754.851372, 28128.826409, 6744803.375884]; const tileCache = new TileCache( process.env.TILECACHE_PATH, @@ -42,37 +37,36 @@ const tileCache = new TileCache( z <= 13 ); -const cachedRenderer = new CachedRenderer( - tileCache, - renderOrStitchRenderer -); +const renderBuildingTile = (t: TileParams, d: any) => renderDataSourceTile(t, d, getBuildingsDataConfig); +const renderHighlightTile = (t: TileParams, d: any) => renderDataSourceTile(t, d, getHighlightDataConfig); -// set up stitch renderer to use the data renderer with caching -stitchRenderer.tileRenderer = cachedRenderer; +function cacheOrCreateBuildingTile(tileParams: TileParams, dataParams: any): Promise { + return getTileWithCaching(tileParams, dataParams, tileCache, stitchOrRenderBuildingTile); +} -const highlightRenderer = new DatasourceRenderer(getHighlightDataConfig); +function stitchOrRenderBuildingTile(tileParams: TileParams, dataParams: any): Promise { + if (tileParams.z <= STITCH_THRESHOLD) { + // stitch tile, using cache recursively + return stitchTile(tileParams, dataParams, cacheOrCreateBuildingTile); + } else { + return renderBuildingTile(tileParams, dataParams); + } +} -const highlightOrBuildingRenderer = new BranchingRenderer( - ({ tileset }) => tileset === 'highlight', - highlightRenderer, - cachedRenderer -); +function renderTile(tileParams: TileParams, dataParams: any): Promise { + if (isOutsideExtent(tileParams, EXTENT_BBOX)) { + return createBlankTile(); + } -const blankRenderer = new BlankRenderer(); + if (tileParams.tileset === 'highlight') { + return renderHighlightTile(tileParams, dataParams); + } -/** - * Hard-code extent so we can short-circuit rendering and return empty/transparent tiles outside the area of interest - * bbox in CRS epsg:3857 in form: [w, s, e, n] - */ -const EXTENT_BBOX: BoundingBox = [-61149.622628, 6667754.851372, 28128.826409, 6744803.375884]; -const mainRenderer = new WindowedRenderer( - EXTENT_BBOX, - highlightOrBuildingRenderer, - blankRenderer -); + return cacheOrCreateBuildingTile(tileParams, dataParams); +} export { allTilesets, - mainRenderer, + renderTile, tileCache }; diff --git a/app/src/tiles/renderers/blankRenderer.ts b/app/src/tiles/renderers/blankRenderer.ts deleted file mode 100644 index 2d3187ed..00000000 --- a/app/src/tiles/renderers/blankRenderer.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Image } from "mapnik"; -import sharp from 'sharp'; - -import { TileParams, TileRenderer } from "../types"; - -class BlankRenderer implements TileRenderer { - getTile(tileParams: TileParams): Promise { - return sharp({ - create: { - width: 1, - height: 1, - channels: 4, - background: { r: 0, g: 0, b: 0, alpha: 0 } - } - }).png().toBuffer(); - } -} - -export { - BlankRenderer -}; diff --git a/app/src/tiles/renderers/branchingRenderer.ts b/app/src/tiles/renderers/branchingRenderer.ts deleted file mode 100644 index ce7d78da..00000000 --- a/app/src/tiles/renderers/branchingRenderer.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Image } from "mapnik"; - -import { TileParams, TileRenderer } from "../types"; - -class BranchingRenderer { - constructor( - public branchTestFn: (tileParams: TileParams) => boolean, - public trueResultTileRenderer: TileRenderer, - public falseResultTileRenderer: TileRenderer - ) {} - - getTile(tileParams: TileParams, dataParams: any): Promise { - if(this.branchTestFn(tileParams)) { - return this.trueResultTileRenderer.getTile(tileParams, dataParams); - } else { - return this.falseResultTileRenderer.getTile(tileParams, dataParams); - } - } -} - -export { - BranchingRenderer -}; diff --git a/app/src/tiles/renderers/cachedRenderer.ts b/app/src/tiles/renderers/cachedRenderer.ts deleted file mode 100644 index 964e75d4..00000000 --- a/app/src/tiles/renderers/cachedRenderer.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Image } from "mapnik"; - -import { TileParams, TileRenderer } from "../types"; -import { TileCache } from "../tileCache"; -import { formatParams } from "../util"; - -class CachedRenderer implements TileRenderer { - constructor( - /** Cache to use for tiles */ - public tileCache: TileCache, - - /** Renderer to use when tile hasn't been cached yet */ - public tileRenderer: TileRenderer - ) {} - - async getTile(tileParams: TileParams, dataParams: any): Promise { - try { - const tile = await this.tileCache.get(tileParams); - return tile; - } catch(err) { - const im = await this.tileRenderer.getTile(tileParams, dataParams); - try { - await this.tileCache.put(im, tileParams); - } catch (err) {} - return im; - } - } -} - -export { - CachedRenderer -}; diff --git a/app/src/tiles/renderers/createBlankTile.ts b/app/src/tiles/renderers/createBlankTile.ts new file mode 100644 index 00000000..9bdaa3e8 --- /dev/null +++ b/app/src/tiles/renderers/createBlankTile.ts @@ -0,0 +1,18 @@ +import sharp from "sharp"; + +import { Tile } from "../types"; + +function createBlankTile(): Promise { + return sharp({ + create: { + width: 1, + height: 1, + channels: 4, + background: { r: 0, g: 0, b: 0, alpha: 0 } + } + }).png().toBuffer(); +} + +export { + createBlankTile +}; diff --git a/app/src/tiles/renderers/datasourceRenderer.ts b/app/src/tiles/renderers/datasourceRenderer.ts deleted file mode 100644 index 674512a5..00000000 --- a/app/src/tiles/renderers/datasourceRenderer.ts +++ /dev/null @@ -1,105 +0,0 @@ -import path from 'path'; - -import mapnik from "mapnik"; - -import { TileParams, TileRenderer } from "../types"; -import { getBbox, TILE_SIZE } from "../util"; -import { promisify } from "util"; - -interface DataConfig { - table: string; - geometry_field: string; -} - -const TILE_BUFFER_SIZE = 64; -const PROJ4_STRING = '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over'; - -// connection details from environment variables -const DATASOURCE_CONFIG = { - 'host': process.env.PGHOST, - 'dbname': process.env.PGDATABASE, - 'user': process.env.PGUSER, - 'password': process.env.PGPASSWORD, - 'port': process.env.PGPORT, - 'extent': '-20005048.4188,-9039211.13765,19907487.2779,17096598.5401', - 'srid': 3857, - 'type': 'postgis' -}; - -// register datasource adapters for mapnik database connection -if (mapnik.register_default_input_plugins) { - mapnik.register_default_input_plugins(); -} -// register fonts for text rendering -mapnik.register_default_fonts(); - - -class DatasourceRenderer implements TileRenderer { - constructor(private getTableDefinitionFn: (tileset: string, dataParams: any) => DataConfig) {} - - async getTile({tileset, z, x, y, scale}: TileParams, dataParams: any): Promise { - const bbox = getBbox(z, x, y); - - const tileSize = TILE_SIZE * scale; - let map = new mapnik.Map(tileSize, tileSize, PROJ4_STRING); - map.bufferSize = TILE_BUFFER_SIZE; - const layer = new mapnik.Layer('tile', PROJ4_STRING); - - const dataSourceConfig = this.getTableDefinitionFn(tileset, dataParams); - - const conf = Object.assign(dataSourceConfig, DATASOURCE_CONFIG); - - const postgis = new mapnik.Datasource(conf); - layer.datasource = postgis; - layer.styles = [tileset]; - - const stylePath = path.join(__dirname, '..', 'map_styles', 'polygon.xml'); - - map = await promisify(map.load.bind(map))(stylePath, {strict: true}); - - map.add_layer(layer); - const im = new mapnik.Image(map.width, map.height); - map.extent = bbox; - const rendered = await promisify(map.render.bind(map))(im, {}); - - return await promisify(rendered.encode.bind(rendered))('png'); - } -} - -function promiseHandler(resolve, reject) { - return function(err, result) { - if(err) reject(err); - else resolve(result); - } -} - -/** - * Utility function which promisifies a method of an object and binds it to the object - * This makes it easier to use callback-based object methods in a promise-based way - * @param obj Object containing the target method - * @param methodName Method name to promisify and return - */ -function promisifyMethod(obj: T, methodName: keyof T); -/** - * @param methodGetter accessor function to get the method from the object - */ -function promisifyMethod(obj: T, methodGetter: (o: T) => S); -function promisifyMethod(obj: T, paramTwo: keyof T | ((o: T) => S)) { - let method; - if (typeof paramTwo === 'string') { - method = obj[paramTwo]; - } else if (typeof paramTwo === 'function') { - method = paramTwo(obj); - } - - if (typeof method === 'function') { - return promisify(method.bind(obj)); - } else { - throw new Error(`Cannot promisify non-function property '${paramTwo}'`); - } -} - -export { - DatasourceRenderer, - DataConfig -}; diff --git a/app/src/tiles/renderers/getTileWithCaching.ts b/app/src/tiles/renderers/getTileWithCaching.ts new file mode 100644 index 00000000..6595d82d --- /dev/null +++ b/app/src/tiles/renderers/getTileWithCaching.ts @@ -0,0 +1,21 @@ +import { TileParams, RendererFunction, Tile } from "../types"; +import { TileCache } from "../tileCache"; + + +async function getTileWithCaching(tileParams: TileParams, dataParams: any, tileCache: TileCache, renderTile: RendererFunction): Promise { + try { + const tile = await tileCache.get(tileParams); + return tile; + } catch (err) { + const im = await renderTile(tileParams, dataParams); + try { + await tileCache.put(im, tileParams); + } catch (err) {} + return im; + } +} + + +export { + getTileWithCaching +}; diff --git a/app/src/tiles/renderers/renderDataSourceTile.ts b/app/src/tiles/renderers/renderDataSourceTile.ts new file mode 100644 index 00000000..9cb7d5e5 --- /dev/null +++ b/app/src/tiles/renderers/renderDataSourceTile.ts @@ -0,0 +1,64 @@ +import path from 'path'; + +import mapnik from "mapnik"; + +import { TileParams, Tile, TableDefinitionFunction } from "../types"; +import { getBbox, TILE_SIZE } from "../util"; +import { promisify } from "util"; + + +const TILE_BUFFER_SIZE = 64; +const PROJ4_STRING = '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over'; + +// connection details from environment variables +const DATASOURCE_CONFIG = { + 'host': process.env.PGHOST, + 'dbname': process.env.PGDATABASE, + 'user': process.env.PGUSER, + 'password': process.env.PGPASSWORD, + 'port': process.env.PGPORT, + 'extent': '-20005048.4188,-9039211.13765,19907487.2779,17096598.5401', + 'srid': 3857, + 'type': 'postgis' +}; + +// register datasource adapters for mapnik database connection +if (mapnik.register_default_input_plugins) { + mapnik.register_default_input_plugins(); +} +// register fonts for text rendering +mapnik.register_default_fonts(); + + +async function renderDataSourceTile({ tileset, z, x, y, scale }: TileParams, dataParams: any, getTableDefinitionFn: TableDefinitionFunction): Promise { + const bbox = getBbox(z, x, y); + + const tileSize = TILE_SIZE * scale; + let map = new mapnik.Map(tileSize, tileSize, PROJ4_STRING); + map.bufferSize = TILE_BUFFER_SIZE; + const layer = new mapnik.Layer('tile', PROJ4_STRING); + + const dataSourceConfig = getTableDefinitionFn(tileset, dataParams); + + const conf = Object.assign(dataSourceConfig, DATASOURCE_CONFIG); + + const postgis = new mapnik.Datasource(conf); + layer.datasource = postgis; + layer.styles = [tileset]; + + const stylePath = path.join(__dirname, '..', 'map_styles', 'polygon.xml'); + + map = await promisify(map.load.bind(map))(stylePath, { strict: true }); + + map.add_layer(layer); + const im = new mapnik.Image(map.width, map.height); + map.extent = bbox; + const rendered = await promisify(map.render.bind(map))(im, {}); + + return await promisify(rendered.encode.bind(rendered))('png'); +} + + +export { + renderDataSourceTile +}; diff --git a/app/src/tiles/renderers/stitchRenderer.ts b/app/src/tiles/renderers/stitchRenderer.ts deleted file mode 100644 index 3264fc48..00000000 --- a/app/src/tiles/renderers/stitchRenderer.ts +++ /dev/null @@ -1,67 +0,0 @@ -import sharp from 'sharp'; -import { Image } from 'mapnik'; - -import { TileParams, TileRenderer } from "../types"; -import { getBbox, getXYZ, TILE_SIZE, formatParams } from "../util"; - -class StitchRenderer implements TileRenderer { - constructor( - /** Renderer to use when retrieving tiles to be stitched together */ - public tileRenderer: TileRenderer - ) {} - - getTile(tileParams: TileParams, dataParams: any): Promise { - console.log(`Stitching tile ${formatParams(tileParams)}`); - return this.stitchTile(tileParams, dataParams, this.tileRenderer); - } - - private async stitchTile({ tileset, z, x, y, scale }: TileParams, dataParams: any, tileRenderer: TileRenderer) { - const bbox = getBbox(z, x, y); - const nextZ = z + 1; - const nextXY = getXYZ(bbox, nextZ); - const tileSize = TILE_SIZE * scale; - - - const [topLeft, topRight, bottomLeft, bottomRight] = await Promise.all([ - [nextXY.minX, nextXY.minY], - [nextXY.maxX, nextXY.minY], - [nextXY.minX, nextXY.maxY], - [nextXY.maxX, nextXY.maxY] - ].map(([x, y]) => tileRenderer.getTile({ tileset, z: nextZ, x, y, scale }, dataParams))); - - // not possible to chain overlays in a single pipeline, but there may still be a better - // way to create image buffer here (four tiles resize to one at the next zoom level) - // instead of repeatedly creating `sharp` objects, to png, to buffer... - return sharp({ - create: { - width: tileSize * 2, - height: tileSize * 2, - channels: 4, - background: { r: 0, g: 0, b: 0, alpha: 0 } - } - }).overlayWith( - topLeft, { gravity: sharp.gravity.northwest } - ).png().toBuffer().then((buf) => { - return sharp(buf).overlayWith( - topRight, { gravity: sharp.gravity.northeast } - ).png().toBuffer() - }).then((buf) => { - return sharp(buf).overlayWith( - bottomLeft, { gravity: sharp.gravity.southwest } - ).png().toBuffer() - }).then((buf) => { - return sharp(buf).overlayWith( - bottomRight, { gravity: sharp.gravity.southeast } - ).png().toBuffer() - }).then((buf) => { - return sharp(buf - ).resize(tileSize, tileSize, { fit: 'inside' } - ).png().toBuffer() - }) - } - -} - -export { - StitchRenderer -}; diff --git a/app/src/tiles/renderers/stitchTile.ts b/app/src/tiles/renderers/stitchTile.ts new file mode 100644 index 00000000..9a08b3b0 --- /dev/null +++ b/app/src/tiles/renderers/stitchTile.ts @@ -0,0 +1,54 @@ +import sharp from 'sharp'; + +import { TileParams, RendererFunction, Tile } from "../types"; +import { getBbox, getXYZ, TILE_SIZE } from "../util"; + + +async function stitchTile({ tileset, z, x, y, scale }: TileParams, dataParams: any, renderTile: RendererFunction): Promise { + const bbox = getBbox(z, x, y); + const nextZ = z + 1; + const nextXY = getXYZ(bbox, nextZ); + const tileSize = TILE_SIZE * scale; + + + const [topLeft, topRight, bottomLeft, bottomRight] = await Promise.all([ + [nextXY.minX, nextXY.minY], + [nextXY.maxX, nextXY.minY], + [nextXY.minX, nextXY.maxY], + [nextXY.maxX, nextXY.maxY] + ].map(([x, y]) => renderTile({ tileset, z: nextZ, x, y, scale }, dataParams))); + + // not possible to chain overlays in a single pipeline, but there may still be a better + // way to create image buffer here (four tiles resize to one at the next zoom level) + // instead of repeatedly creating `sharp` objects, to png, to buffer... + return sharp({ + create: { + width: tileSize * 2, + height: tileSize * 2, + channels: 4, + background: { r: 0, g: 0, b: 0, alpha: 0 } + } + }).overlayWith( + topLeft, { gravity: sharp.gravity.northwest } + ).png().toBuffer().then((buf) => { + return sharp(buf).overlayWith( + topRight, { gravity: sharp.gravity.northeast } + ).png().toBuffer() + }).then((buf) => { + return sharp(buf).overlayWith( + bottomLeft, { gravity: sharp.gravity.southwest } + ).png().toBuffer() + }).then((buf) => { + return sharp(buf).overlayWith( + bottomRight, { gravity: sharp.gravity.southeast } + ).png().toBuffer() + }).then((buf) => { + return sharp(buf + ).resize(tileSize, tileSize, { fit: 'inside' } + ).png().toBuffer() + }) +} + +export { + stitchTile +}; diff --git a/app/src/tiles/renderers/windowedRenderer.ts b/app/src/tiles/renderers/windowedRenderer.ts deleted file mode 100644 index b63e901e..00000000 --- a/app/src/tiles/renderers/windowedRenderer.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Image } from "mapnik"; - -import { BoundingBox, TileParams, TileRenderer } from "../types"; -import { getXYZ } from "../util"; - -class WindowedRenderer implements TileRenderer { - constructor( - /** Bounding box defining the renderer window */ - public bbox: BoundingBox, - - /** Renderer to use for tile requests inside window */ - public insideWindowRenderer: TileRenderer, - - /** Renderer to use for tile requests outside window */ - public outsideWindowRenderer: TileRenderer - ) {} - - getTile(tileParams: TileParams, dataParams: any): Promise { - if(this.isOutsideExtent(tileParams)) { - return this.outsideWindowRenderer.getTile(tileParams, dataParams); - } else { - return this.insideWindowRenderer.getTile(tileParams, dataParams); - } - } - - private isOutsideExtent({x, y, z}: TileParams) { - const xy = getXYZ(this.bbox, z); - return xy.minY > y || xy.maxY < y || xy.minX > x || xy.maxX < x; - } -} - -export { - WindowedRenderer -}; diff --git a/app/src/tiles/tileserver.ts b/app/src/tiles/tileserver.ts index 0eee4a20..9ebf101a 100644 --- a/app/src/tiles/tileserver.ts +++ b/app/src/tiles/tileserver.ts @@ -7,7 +7,7 @@ import express from 'express'; import { strictParseInt } from '../parse'; import { TileParams } from './types'; -import { mainRenderer, allTilesets } from './rendererDefinition'; +import { renderTile, allTilesets } from './rendererDefinition'; import asyncController from '../api/routes/asyncController'; const handleTileRequest = asyncController(async function (req: express.Request, res: express.Response) { @@ -20,7 +20,7 @@ const handleTileRequest = asyncController(async function (req: express.Request, } try { - const im = await mainRenderer.getTile(tileParams, dataParams); + const im = await renderTile(tileParams, dataParams); res.writeHead(200, { 'Content-Type': 'image/png' }); res.end(im); } catch(err) { diff --git a/app/src/tiles/types.ts b/app/src/tiles/types.ts index d2fccbef..eef82868 100644 --- a/app/src/tiles/types.ts +++ b/app/src/tiles/types.ts @@ -1,4 +1,5 @@ import { Image } from 'mapnik'; +import { Sharp } from 'sharp'; /** * Bounding box in the format [w, s, e, n] @@ -32,12 +33,26 @@ interface TileParams { scale: number; } +interface DataConfig { + table: string; + geometry_field: string; +} + +type TableDefinitionFunction = (tileset: string, dataParams: any) => DataConfig; + +type Tile = Image | Sharp; +type RendererFunction = (tileParams: TileParams, dataParams: any) => Promise; + interface TileRenderer { - getTile(tileParams: TileParams, dataParams: any): Promise + getTile: RendererFunction } export { BoundingBox, TileParams, - TileRenderer + TileRenderer, + Tile, + RendererFunction, + DataConfig, + TableDefinitionFunction }; diff --git a/app/src/tiles/util.ts b/app/src/tiles/util.ts index 46279cd4..42f4dab9 100644 --- a/app/src/tiles/util.ts +++ b/app/src/tiles/util.ts @@ -1,6 +1,6 @@ import SphericalMercator from '@mapbox/sphericalmercator'; -import { TileParams } from './types'; +import { TileParams, BoundingBox } from './types'; const TILE_SIZE = 256; @@ -20,9 +20,15 @@ function formatParams({ tileset, z, x, y, scale }: TileParams): string { return `${tileset}/${z}/${x}/${y}@${scale}x`; } +function isOutsideExtent({ x, y, z }: TileParams, bbox: BoundingBox) { + const xy = getXYZ(bbox, z); + return xy.minY > y || xy.maxY < y || xy.minX > x || xy.maxX < x; +} + export { TILE_SIZE, getBbox, getXYZ, - formatParams + formatParams, + isOutsideExtent };