UNPKG

@opengis/bi

Version:

BI data visualization module

167 lines (126 loc) 5.2 kB
import Sphericalmercator from '@mapbox/sphericalmercator'; import path from 'path'; import { createHash } from 'crypto'; import { writeFile, mkdir } from 'fs/promises'; import { logger, getFolder, getFilterSQL, autoIndex, pgClients } from '@opengis/fastify-table/utils.js'; import { getWidget } from '../../../../utils.js'; import downloadClusterData from './utils/downloadClusterData.js'; const mercator = new Sphericalmercator({ size: 256 }); const clusterExists = {}; export default async function clusterVtile(req, reply) { const { params = {}, query = {} } = req; const { z, y } = params; const x = params.x?.split('.')[0] - 0; if (!x || !y || !z) { return { message: 'not enough params: xyz', status: 400 }; } const { widget, filter, dashboard, search, clusterZoom, nocache, pointZoom } = query; if (!widget) { return { message: 'not enough params: widget', status: 400 }; } const { pg = req.pg || pgClients.client, data } = await getWidget({ pg: req.pg, dashboard, widget }); const headers = { 'Content-Type': 'application/x-protobuf', 'Cache-Control': nocache || query.sql ? 'no-cache' : 'public, max-age=86400', }; const hash = [pointZoom, filter].filter((el) => el).join(); const root = getFolder(req); const file = path.join( root, `/map/vtile/${widget}/${hash ? `${createHash('sha1').update(hash).digest('base64')}/` : ''}${z}/${x}/${y}.mvt` ); try { if (!data?.table) { return { message: `invalid ${widget ? 'widget' : 'dashboard'}: table not specified`, status: 400, }; } const pkey = pg.pk?.[data?.table]; if (!pkey) { return { message: `invalid ${widget ? 'widget' : 'dashboard'}: table pk not found (${data?.table})`, status: 400, }; } // data param const { table, query: where = '1=1', metrics = [], cluster, clusterTable = {}, } = data; if (!clusterTable?.name) { Object.assign(clusterTable, { name: 'bi.cluster', title: 'title', query: `type='${data.cluster}'`, }); } if (cluster && !clusterExists[data.cluster]) { const res = await downloadClusterData({ pg, cluster }); if (res) return res; clusterExists[cluster] = 1; } if (!cluster) { return { message: `invalid ${widget ? 'widget' : 'dashboard'}: cluster column not specified`, status: 400, }; } if (!metrics.length) { return { message: `invalid ${widget ? 'widget' : 'dashboard'}: metric columns not found`, status: 400, }; } // get sql const { optimizedSQL } = filter || search ? await getFilterSQL({ pg, table, filter, search }) : {}; const q = `select ${clusterTable?.column || cluster} as name, ${clusterTable?.operator || 'sum'}("${metrics[0]}")::float as metric, b.* from ${optimizedSQL ? `(${optimizedSQL})` : table} q left join lateral (select "${pg.pk?.[clusterTable?.name]}" as id, ${clusterTable?.title} as title, ${clusterTable?.geom || 'geom'} as geom from ${clusterTable?.name} where ${clusterTable?.query || '1=1'} and ${clusterTable?.codifierColumn || 'codifier'}=q."${clusterTable?.column || cluster}" limit 1 )b on 1=1 where ${where} group by q."${clusterTable?.column || cluster}", b.id, b.title, b.geom`; if (query.sql === '1') return q; const geomCol = parseInt(z, 10) < parseInt(pointZoom, 10) ? `ST_Centroid(${clusterTable?.geom || data?.geom || 'geom'})` : clusterTable?.geom || data?.geom || 'geom'; const bbox = mercator.bbox(+y, +x, +z, false /* , '900913' */); const bbox2d = `'BOX(${bbox[0]} ${bbox[1]},${bbox[2]} ${bbox[3]})'::box2d`; const q1 = `SELECT ST_AsMVT(q, 'bi', 4096, 'geom','row') as tile FROM ( SELECT floor(random() * 100000 + 1)::int + row_number() over() as row, ${pg.pk?.[clusterTable?.name] ? 'id,' : ''} name, metric, title, ST_AsMVTGeom(st_transform(${geomCol}, 3857),ST_TileEnvelope(${z},${y},${x})::box2d,4096,256,false) as geom FROM (select * from (${q})q where geom && ${bbox2d} and geom is not null and st_srid(geom) >0 and ST_GeometryType(geom) = any ('{ "ST_Polygon", "ST_MultiPolygon" }') limit 3000)q ) q`; if (query.sql === '2') return q1; // auto Index autoIndex({ table, columns: (metrics || []).concat([cluster]) }); const { rows = [] } = await pg.query(q1); if (query.sql === '3') return rows.map((el) => el.tile); const buffer = Buffer.concat(rows.map((el) => Buffer.from(el.tile))); if (!nocache) { await mkdir(path.dirname(file), { recursive: true }); await writeFile(file, buffer, 'binary'); } return reply.headers(headers).send(buffer); } catch (err) { logger.file('bi/clusterVtile/error', { error: err.toString(), query, params }); return { error: err.toString(), status: 500 }; } }