@opengis/bi
Version:
BI data visualization module
128 lines (103 loc) • 4.3 kB
JavaScript
import path from 'path';
import { createHash } from 'crypto';
import { writeFile, mkdir, readFile, stat } from 'fs/promises';
import { existsSync, /* readdirSync, */ readFileSync } from 'fs';
import { getFolder, getFilterSQL, autoIndex, logger, pgClients } from '@opengis/fastify-table/utils.js';
import normalizeData from '../../data/controllers/util/normalizeData.js';
import { getWidget } from '../../../../utils.js';
const types = {
point: 'ST_Point' /* ,ST_MultiPoint */,
polygon: 'ST_Polygon,ST_MultiPolygon',
};
const hourMs = 3.6e6;
export default async function geojson(req, reply) {
const { query = {} } = req;
const {
filter,
widget,
sql,
type,
nocache,
id,
dashboard,
geom = 'geom',
pointZoom = 0,
} = query;
if (!widget && !dashboard) {
return { message: 'not enough params: widget', status: 400 };
}
const data = await getWidget({ pg: req.pg, dashboard, widget });
if (data.status) return data;
const pg = data.pg || req.pg || pgClients.client;
const hash = [pointZoom, filter].filter((el) => el).join();
const root = getFolder(req);
const file = path.join(
root,
`/map/geojson/${widget}/${hash ? `${createHash('sha1').update(hash).digest('base64')}/` : ''}.geojson`
);
if (existsSync(file)) {
const timeNow = Date.now();
const stats = await stat(file);
const birthTime = new Date(stats.birthtime).getTime();
if (!(birthTime - timeNow > hourMs * 24) && !nocache) {
const res = JSON.parse((await readFile(file, 'utf-8')) || {});
return res;
}
}
try {
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, where = '1=1', xName, x } = normalizeData(data, query);
if (!xName && !x) {
return {
message: `invalid ${widget ? 'widget' : 'dashboard'}: x axis column not specified`,
status: 400,
};
}
// get sql
const filterQ = filter
? await getFilterSQL({ pg, table, filter, query })
: undefined;
const q = `select "${pkey}", "${xName || x}", /* st_asgeojson(geom)::json as */ ${geom} as geom from ${filterQ ? `(${filterQ})` : table} q where ${where}`;
if (sql === '1') return q;
const { st_geometrytype: geomType = 'point' } = await pg
.query(
`select st_geometrytype(${geom}), count(*) from ${table}
where ${where} group by st_geometrytype(${geom})`
)
.then((res) => res.rows?.[0] || {});
const q1 = `SELECT 'FeatureCollection' As type, json_agg(f) As features FROM (
SELECT 'Feature' As type, row_number() over() as id,
st_asgeojson(st_force2d(${query.srid
? `st_transform(${type === 'centroid' ? `st_centroid(${geom})` : geom},${query.srid})`
: `${type === 'centroid' || query.point || query.centroid ? `st_centroid(${geom})` : geom}`
}), 6, 0)::json as geometry,
(select row_to_json(tc) from (select ${'' ? `${''} as status, ` : ''}
${xName ? `${xName},` : ''}
${data.style?.colorAttr ? `${data.style.colorAttr},` : ''}
${pkey} as id,(select file_path from crm.files
where entity_id=q.${pkey}::text and file_status <>'3' and ext in ('png','jpg') limit 1) as image
)tc) as properties
from (${q})q where ${id && pkey ? ` ${pkey} = '${id}' and ` : ''} ${geom} is not null
${data.query ? ` and ${data.query}` : ''}
${query.extent ? `and ${geom} && 'BOX(${query.extent.split(',').reduce((p, el, i) => p + el + (i % 2 ? ',' : ' '), '')})'::box2d` : ''}
${types[type] ? ` and ST_GeometryType(${geom}) = any ('{ ${types[type]} }') ` : ''}
limit ${geomType?.toLowerCase()?.includes('point') ? '15000' : '2500'})f`;
if (sql === '2') return q1;
// auto Index
autoIndex({ table, columns: [xName] });
const res = await pg.query(q1).then((res) => res.rows?.[0] || {});
await mkdir(path.dirname(file), { recursive: true });
await writeFile(file, JSON.stringify(res));
return res;
} catch (err) {
logger.file('bi/geojson', { level: 'ERROR', error: err.toString(), query });
return { error: err.toString(), status: 500 };
}
}