@opengis/bi 1.0.13 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -4
- package/config.js +5 -5
- package/dist/bi.js +1 -1
- package/dist/bi.umd.cjs +116 -130
- package/dist/{import-file-1T7kpSzt.js → import-file-DUp3rsNI.js} +11132 -10748
- package/dist/{map-component-mixin-BLM9iEWA.js → map-component-mixin-CGM0P5ub.js} +1135 -1134
- package/dist/style.css +1 -1
- package/dist/{vs-calendar-WiK1hcHS.js → vs-calendar-cOoinEwc.js} +33 -30
- package/dist/vs-funnel-bar-kLkPoIhJ.js +105 -0
- package/dist/vs-heatmap-3XAVGTSo.js +98 -0
- package/dist/vs-map-B1tr6V5_.js +74 -0
- package/dist/{vs-map-cluster-Dfe9INqE.js → vs-map-cluster-BWJPx7wE.js} +28 -25
- package/dist/vs-number-CrU7LmkV.js +48 -0
- package/dist/{vs-text-DcrAdQ40.js → vs-text-DRPx3aID.js} +2 -1
- package/package.json +37 -12
- package/plugin.js +4 -4
- package/server/migrations/bi.sql +66 -0
- package/server/plugins/docs.js +36 -35
- package/server/plugins/hook.js +72 -69
- package/server/plugins/vite.js +22 -8
- package/server/routes/dashboard/controllers/dashboard.delete.js +5 -3
- package/server/routes/dashboard/controllers/dashboard.js +66 -32
- package/server/routes/dashboard/controllers/dashboard.list.js +2 -5
- package/server/routes/dashboard/controllers/utils/yaml.js +1 -2
- package/server/routes/dashboard/index.mjs +5 -4
- package/server/routes/data/controllers/data.js +94 -34
- package/server/routes/data/controllers/util/chartSQL.js +24 -10
- package/server/routes/data/controllers/util/normalizeData.js +51 -29
- package/server/routes/data/index.mjs +1 -3
- package/server/routes/db/controllers/dbTablePreview.js +63 -0
- package/server/routes/db/controllers/dbTables.js +36 -0
- package/server/routes/db/index.mjs +17 -0
- package/server/routes/edit/controllers/dashboard.add.js +6 -5
- package/server/routes/edit/controllers/dashboard.edit.js +16 -9
- package/server/routes/edit/controllers/widget.add.js +43 -19
- package/server/routes/edit/controllers/widget.del.js +13 -6
- package/server/routes/edit/controllers/widget.edit.js +34 -13
- package/server/routes/edit/index.mjs +14 -10
- package/server/routes/map/controllers/cluster.js +89 -60
- package/server/routes/map/controllers/clusterVtile.js +154 -84
- package/server/routes/map/controllers/geojson.js +48 -22
- package/server/routes/map/controllers/map.js +51 -51
- package/server/routes/map/controllers/vtile.js +61 -40
- package/server/routes/map/index.mjs +1 -1
- package/server/utils/getWidget.js +67 -40
- package/utils.js +5 -4
- package/dist/vs-funnel-bar-CpPbYZ0_.js +0 -92
- package/dist/vs-heatmap-BG4eIROH.js +0 -83
- package/dist/vs-map-BRk6Fmks.js +0 -66
- package/dist/vs-number-CJq-vi95.js +0 -39
|
@@ -1,11 +1,22 @@
|
|
|
1
|
-
import dataUpdate from '@opengis/fastify-table/
|
|
2
|
-
|
|
1
|
+
import { dataUpdate } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
|
|
3
|
+
import { yamlSafe } from '../../../../utils.js';
|
|
4
|
+
|
|
5
|
+
export default async function widgetEdit({
|
|
6
|
+
pg = {},
|
|
7
|
+
funcs = {},
|
|
8
|
+
body = {},
|
|
9
|
+
params = {},
|
|
10
|
+
}) {
|
|
3
11
|
const { widget: widgetName, name: dashboardName } = params;
|
|
4
|
-
const
|
|
12
|
+
const data = body.yml ? yamlSafe.load(body.yml) : body;
|
|
13
|
+
const tableName =
|
|
14
|
+
data.data?.table || data?.table || data.table_name || body.table_name;
|
|
15
|
+
|
|
5
16
|
if (!widgetName || !dashboardName) {
|
|
6
17
|
return {
|
|
7
18
|
message: 'not enough params: dashboard and widget name',
|
|
8
|
-
status: 400
|
|
19
|
+
status: 400,
|
|
9
20
|
};
|
|
10
21
|
}
|
|
11
22
|
|
|
@@ -17,23 +28,33 @@ export default async function widgetEdit({ pg = {}, funcs = {}, body = {}, param
|
|
|
17
28
|
[dashboardName, widgetName]
|
|
18
29
|
)
|
|
19
30
|
.then((res1) => res1.rows?.[0] || {});
|
|
20
|
-
const checkTable = await pg.query(`select * from bi.widget where $1 in (table_name)`, [
|
|
21
|
-
tableName
|
|
22
|
-
]);
|
|
23
31
|
const { widget_id: widgetId, dashboard_id: dashboardId } = row || {};
|
|
24
|
-
|
|
32
|
+
|
|
33
|
+
// const checkTable = await pg.query(
|
|
34
|
+
// `select * from bi.widget where $1 in (table_name)`,
|
|
35
|
+
// [tableName]
|
|
36
|
+
// );
|
|
37
|
+
// if (!checkTable.rows.length) {
|
|
38
|
+
// return { message: 'bad params', status: 401 };
|
|
39
|
+
// }
|
|
40
|
+
|
|
41
|
+
if (!tableName || !pg.pk?.[tableName]) {
|
|
42
|
+
return { message: 'bad params: table', status: 400 };
|
|
43
|
+
}
|
|
25
44
|
|
|
26
45
|
if (!widgetId) {
|
|
27
46
|
return { message: `widget not found ${widgetName}`, status: 404 };
|
|
28
47
|
}
|
|
29
48
|
|
|
30
|
-
const
|
|
49
|
+
const widgetData = { ...data, data };
|
|
50
|
+
if (body?.yml) Object.assign(widgetData, { yml: body.yml });
|
|
51
|
+
await funcs.dataUpdate({
|
|
31
52
|
table: 'bi.widget',
|
|
32
53
|
id: widgetId,
|
|
33
|
-
data:
|
|
54
|
+
data: widgetData,
|
|
34
55
|
});
|
|
35
56
|
|
|
36
|
-
|
|
57
|
+
// \====================
|
|
37
58
|
const currentDashboard = await pg
|
|
38
59
|
.query(
|
|
39
60
|
`select * from bi.dashboard
|
|
@@ -68,13 +89,13 @@ export default async function widgetEdit({ pg = {}, funcs = {}, body = {}, param
|
|
|
68
89
|
const res1 = await dataUpdate({
|
|
69
90
|
table: 'bi.dashboard',
|
|
70
91
|
id: dashboardId,
|
|
71
|
-
data: bodyDashboard
|
|
92
|
+
data: bodyDashboard,
|
|
72
93
|
});
|
|
73
94
|
|
|
74
95
|
return {
|
|
75
96
|
message: `Edited widget ${widgetName}`,
|
|
76
97
|
status: 200,
|
|
77
|
-
rows: res1
|
|
98
|
+
rows: res1,
|
|
78
99
|
};
|
|
79
100
|
} catch (err) {
|
|
80
101
|
return { error: err.toString(), status: 500 };
|
|
@@ -1,27 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
1
|
+
/* eslint-disable import/extensions */
|
|
2
|
+
import widgetAdd from './controllers/widget.add.js';
|
|
3
|
+
import widgetEdit from './controllers/widget.edit.js';
|
|
4
|
+
import widgetDel from './controllers/widget.del.js';
|
|
5
|
+
import dashboardEdit from './controllers/dashboard.edit.js';
|
|
6
|
+
import dashboardAdd from './controllers/dashboard.add.js';
|
|
6
7
|
|
|
7
8
|
const biSchema = {
|
|
8
9
|
querystring: {
|
|
9
10
|
widget: { type: 'string', pattern: '^([\\d\\w]+)$' },
|
|
10
11
|
dashboard: { type: 'string', pattern: '^([\\d\\w]+)$' },
|
|
11
12
|
list: { type: 'string', pattern: '^([\\d])$' },
|
|
12
|
-
sql: { type: 'string', pattern: '^([\\d])$' }
|
|
13
|
+
sql: { type: 'string', pattern: '^([\\d])$' },
|
|
13
14
|
},
|
|
14
15
|
params: {
|
|
15
16
|
name: { type: 'string', pattern: '^([\\d\\w]+)$' },
|
|
16
|
-
widget: { type: 'string', pattern: '^([\\d\\w]+)$' }
|
|
17
|
+
widget: { type: 'string', pattern: '^([\\d\\w]+)$' },
|
|
17
18
|
},
|
|
18
19
|
};
|
|
19
20
|
|
|
20
|
-
|
|
21
21
|
export default async function route(fastify) {
|
|
22
22
|
fastify.post(`/bi-dashboard/:name`, { schema: biSchema }, widgetAdd);
|
|
23
23
|
fastify.put(`/bi-dashboard/:name/:widget`, { schema: biSchema }, widgetEdit);
|
|
24
|
-
fastify.delete(
|
|
24
|
+
fastify.delete(
|
|
25
|
+
`/bi-dashboard/:name/:widget`,
|
|
26
|
+
{ schema: biSchema },
|
|
27
|
+
widgetDel
|
|
28
|
+
);
|
|
25
29
|
fastify.post(`/bi-dashboard`, { schema: biSchema }, dashboardAdd);
|
|
26
30
|
fastify.put(`/bi-dashboard/:name`, { schema: biSchema }, dashboardEdit);
|
|
27
|
-
}
|
|
31
|
+
}
|
|
@@ -1,75 +1,104 @@
|
|
|
1
|
-
|
|
2
1
|
import { getWidget } from '../../../../utils.js';
|
|
3
2
|
|
|
4
|
-
export default async function cluster({
|
|
5
|
-
|
|
6
|
-
}) {
|
|
7
|
-
const {
|
|
8
|
-
widget, filter, dashboard, search,
|
|
9
|
-
} = query;
|
|
10
|
-
|
|
11
|
-
if (!widget) {
|
|
12
|
-
return { message: 'not enough params: widget', status: 400 };
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const { data } = await getWidget({ dashboard, widget })
|
|
3
|
+
export default async function cluster({ pg, funcs, query = {}, log }) {
|
|
4
|
+
const { widget, filter, dashboard, search } = query;
|
|
16
5
|
|
|
17
|
-
|
|
6
|
+
if (!widget) {
|
|
7
|
+
return { message: 'not enough params: widget', status: 400 };
|
|
8
|
+
}
|
|
18
9
|
|
|
19
|
-
|
|
10
|
+
const { data } = await getWidget({ dashboard, widget });
|
|
20
11
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
12
|
+
try {
|
|
13
|
+
const pkey = pg.pk?.[data?.table];
|
|
24
14
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (!metrics.length) {
|
|
33
|
-
return { message: `invalid ${widget ? 'widget' : 'dashboard'}: metric columns not found`, status: 400 };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (!clusterTable?.name) {
|
|
37
|
-
return { message: 'not enough widget params: clusterTable name', status: 400 };
|
|
38
|
-
}
|
|
15
|
+
if (!pkey) {
|
|
16
|
+
return {
|
|
17
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: table pk not found (${data?.table})`,
|
|
18
|
+
status: 400,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
39
21
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
22
|
+
// data param
|
|
23
|
+
const {
|
|
24
|
+
table,
|
|
25
|
+
query: where = '1=1',
|
|
26
|
+
metrics = [],
|
|
27
|
+
cluster,
|
|
28
|
+
clusterTable = {},
|
|
29
|
+
} = data;
|
|
30
|
+
|
|
31
|
+
if (!cluster) {
|
|
32
|
+
return {
|
|
33
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: cluster column not specified`,
|
|
34
|
+
status: 400,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
43
37
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
if (!metrics.length) {
|
|
39
|
+
return {
|
|
40
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: metric columns not found`,
|
|
41
|
+
status: 400,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
48
44
|
|
|
49
|
-
|
|
45
|
+
if (!clusterTable?.name) {
|
|
46
|
+
return {
|
|
47
|
+
message: 'not enough widget params: clusterTable name',
|
|
48
|
+
status: 400,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
if (clusterTable?.name && !pg.pk?.[clusterTable?.name]) {
|
|
53
|
+
return {
|
|
54
|
+
message: 'invalid widget params: clusterTable pkey not found',
|
|
55
|
+
status: 404,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
55
58
|
|
|
56
|
-
|
|
59
|
+
const { total = 0 } = pg.pk?.[table]
|
|
60
|
+
? await pg
|
|
61
|
+
.queryCache(
|
|
62
|
+
`select oid::regclass as table, reltuples AS total from pg_class`
|
|
63
|
+
)
|
|
64
|
+
.then((res) => res.rows?.find((row) => row.table === table))
|
|
65
|
+
: {};
|
|
66
|
+
|
|
67
|
+
const { bbox } = await pg
|
|
68
|
+
.query(
|
|
69
|
+
`select st_asgeojson(box2d(geom))::json as bbox from ${table} where ${where || '1=1'}`
|
|
70
|
+
)
|
|
71
|
+
.then((res1) => res1.rows?.[0] || {});
|
|
72
|
+
|
|
73
|
+
// get sql
|
|
74
|
+
const { optimizedSQL } =
|
|
75
|
+
filter || search
|
|
76
|
+
? await funcs.getFilterSQL({ pg, table, filter, search })
|
|
77
|
+
: {};
|
|
78
|
+
|
|
79
|
+
const q = `select "${cluster}" as name, sum("${metrics[0]}")::float as metric
|
|
57
80
|
from ${optimizedSQL ? `(${optimizedSQL})` : table} q
|
|
58
81
|
left join lateral (select "${pg.pk?.[clusterTable?.name]}" as id from ${clusterTable?.name} where ${clusterTable?.title}=q."${cluster}" limit 1)b on 1=1
|
|
59
82
|
where ${where} group by ${cluster}, b.id`;
|
|
60
83
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
if (query.sql === '1') return q;
|
|
85
|
+
|
|
86
|
+
// auto Index
|
|
87
|
+
// funcs.autoIndex({ table, columns: (metrics || []).concat([cluster]) });
|
|
88
|
+
|
|
89
|
+
const { rows = [] } = await pg.query(q);
|
|
90
|
+
const vals = rows.map((el) => el.metric - 0).sort((a, b) => a - b);
|
|
91
|
+
const len = vals.length;
|
|
92
|
+
const sizes = [
|
|
93
|
+
vals[0],
|
|
94
|
+
vals[Math.floor(len / 4)],
|
|
95
|
+
vals[Math.floor(len / 2)],
|
|
96
|
+
vals[Math.floor(len * 0.75)],
|
|
97
|
+
vals[len - 1],
|
|
98
|
+
];
|
|
99
|
+
return { sizes, rows, bbox, count: rows.length, total };
|
|
100
|
+
} catch (err) {
|
|
101
|
+
log.error('bi/cluster', { error: err.toString(), query });
|
|
102
|
+
return { error: err.toString(), status: 500 };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -11,81 +11,148 @@ const mercator = new Sphericalmercator({ size: 256 });
|
|
|
11
11
|
const clusterExists = {};
|
|
12
12
|
|
|
13
13
|
export default async function clusterVtile(req, reply) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
const { pg, funcs, params = {}, query = {}, log } = req;
|
|
15
|
+
const { z, y } = params;
|
|
16
|
+
const x = params.x?.split('.')[0] - 0;
|
|
17
|
+
|
|
18
|
+
if (!x || !y || !z) {
|
|
19
|
+
return { message: 'not enough params: xyz', status: 400 };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { widget, filter, dashboard, search, clusterZoom, nocache, pointZoom } =
|
|
23
|
+
query;
|
|
24
|
+
|
|
25
|
+
if (!widget) {
|
|
26
|
+
return { message: 'not enough params: widget', status: 400 };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { data } = await getWidget({ dashboard, widget });
|
|
30
|
+
|
|
31
|
+
const headers = {
|
|
32
|
+
'Content-Type': 'application/x-protobuf',
|
|
33
|
+
'Cache-Control':
|
|
34
|
+
nocache || query.sql ? 'no-cache' : 'public, max-age=86400',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const hash = [pointZoom, filter].filter((el) => el).join();
|
|
38
|
+
|
|
39
|
+
const root = funcs.getFolder(req);
|
|
40
|
+
const file = path.join(
|
|
41
|
+
root,
|
|
42
|
+
`/map/vtile/${widget}/${hash ? `${createHash('sha1').update(hash).digest('base64')}/` : ''}${z}/${x}/${y}.mvt`
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
if (!data?.table) {
|
|
47
|
+
return {
|
|
48
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: table not specified`,
|
|
49
|
+
status: 400,
|
|
50
|
+
};
|
|
22
51
|
}
|
|
23
52
|
|
|
24
|
-
const
|
|
25
|
-
widget, filter, dashboard, search, clusterZoom, nocache, pointZoom,
|
|
26
|
-
} = query;
|
|
53
|
+
const pkey = pg.pk?.[data?.table];
|
|
27
54
|
|
|
28
|
-
if (!
|
|
29
|
-
|
|
55
|
+
if (!pkey) {
|
|
56
|
+
return {
|
|
57
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: table pk not found (${data?.table})`,
|
|
58
|
+
status: 400,
|
|
59
|
+
};
|
|
30
60
|
}
|
|
31
61
|
|
|
62
|
+
// data param
|
|
63
|
+
const {
|
|
64
|
+
table,
|
|
65
|
+
query: where = '1=1',
|
|
66
|
+
metrics = [],
|
|
67
|
+
cluster,
|
|
68
|
+
clusterTable = {},
|
|
69
|
+
} = data;
|
|
70
|
+
if (!clusterTable?.name) {
|
|
71
|
+
Object.assign(clusterTable, {
|
|
72
|
+
name: 'bi.cluster',
|
|
73
|
+
title: 'title',
|
|
74
|
+
query: `type='${data.cluster}'`,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
32
77
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return { message: `invalid ${widget ? 'widget' : 'dashboard'}: table pk not found (${data?.table})`, status: 400 };
|
|
78
|
+
if (cluster && !clusterExists[data.cluster]) {
|
|
79
|
+
const res = await fetch(
|
|
80
|
+
`https://cdn.softpro.ua/data/bi/${data.cluster}-ua.geojson`
|
|
81
|
+
);
|
|
82
|
+
if (res?.status !== 200) {
|
|
83
|
+
return {
|
|
84
|
+
message: `cluster file not found: ${cluster}-ua.json`,
|
|
85
|
+
status: 404,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const geojson = await res.json();
|
|
90
|
+
|
|
91
|
+
const features = geojson?.features?.filter(
|
|
92
|
+
(el, idx, arr) =>
|
|
93
|
+
el?.geometry &&
|
|
94
|
+
arr
|
|
95
|
+
.map((item) => item.properties.name)
|
|
96
|
+
.indexOf(el.properties.name) === idx
|
|
97
|
+
); // unique
|
|
98
|
+
if (!features?.length) {
|
|
99
|
+
return {
|
|
100
|
+
message: `cluster file empty: ${cluster}-ua.json`,
|
|
101
|
+
status: 400,
|
|
102
|
+
};
|
|
59
103
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
104
|
+
const { count = 0 } = await pg
|
|
105
|
+
.query(`select count(*)::int from bi.cluster where type=$1`, [
|
|
106
|
+
cluster,
|
|
107
|
+
])
|
|
108
|
+
.then((res1) => res1.rows?.[0] || {});
|
|
109
|
+
if (count !== features?.length) {
|
|
110
|
+
// await pg.query(`delete from bi.cluster where type=$1`, [cluster]);
|
|
111
|
+
const values = features
|
|
112
|
+
?.map(
|
|
113
|
+
(el) =>
|
|
114
|
+
`('${el.properties.name?.replace(/'/g, "''") || ''}', '${cluster}', ST_GeomFromGeoJSON('${JSON.stringify(el.geometry)}')::geometry)`
|
|
115
|
+
)
|
|
116
|
+
.join(',');
|
|
117
|
+
const { rowCount } = await pg.query(
|
|
118
|
+
`insert into bi.cluster (title,type,geom) values ${values} on conflict(title,type) do update set geom=excluded.geom`
|
|
119
|
+
);
|
|
120
|
+
log.info('bi/clusterVtile', { cluster, rowCount });
|
|
73
121
|
}
|
|
122
|
+
clusterExists[cluster] = 1;
|
|
123
|
+
} catch (err) {
|
|
124
|
+
log.error('bi/clusterVtile', {
|
|
125
|
+
error: err.toString(),
|
|
126
|
+
filename: `${cluster}-ua.json`,
|
|
127
|
+
});
|
|
128
|
+
return {
|
|
129
|
+
message: `cluster file import error: ${cluster}-ua.json`,
|
|
130
|
+
status: 500,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
74
134
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
135
|
+
if (!cluster) {
|
|
136
|
+
return {
|
|
137
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: cluster column not specified`,
|
|
138
|
+
status: 400,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
78
141
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
142
|
+
if (!metrics.length) {
|
|
143
|
+
return {
|
|
144
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: metric columns not found`,
|
|
145
|
+
status: 400,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
82
148
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
149
|
+
// get sql
|
|
150
|
+
const { optimizedSQL } =
|
|
151
|
+
filter || search
|
|
152
|
+
? await funcs.getFilterSQL({ pg, table, filter, search })
|
|
153
|
+
: {};
|
|
87
154
|
|
|
88
|
-
|
|
155
|
+
const q = `select "${cluster}" as name, sum("${metrics[0]}")::float as metric, b.*
|
|
89
156
|
from ${optimizedSQL ? `(${optimizedSQL})` : table} q
|
|
90
157
|
left join lateral (select "${pg.pk?.[clusterTable?.name]}" as id,
|
|
91
158
|
${clusterTable?.geom || 'geom'} as geom from ${clusterTable?.name}
|
|
@@ -94,14 +161,17 @@ export default async function clusterVtile(req, reply) {
|
|
|
94
161
|
where ${where} group by
|
|
95
162
|
${cluster}, b.id, b.${clusterTable?.geom || 'geom'}`;
|
|
96
163
|
|
|
97
|
-
|
|
164
|
+
if (query.sql === '1') return q;
|
|
98
165
|
|
|
99
|
-
|
|
166
|
+
const geomCol =
|
|
167
|
+
parseInt(z, 10) < parseInt(pointZoom, 10)
|
|
168
|
+
? `ST_Centroid(${clusterTable?.geom || data?.geom || 'geom'})`
|
|
169
|
+
: clusterTable?.geom || data?.geom || 'geom';
|
|
100
170
|
|
|
101
|
-
|
|
102
|
-
|
|
171
|
+
const bbox = mercator.bbox(+y, +x, +z, false /* , '900913' */);
|
|
172
|
+
const bbox2d = `'BOX(${bbox[0]} ${bbox[1]},${bbox[2]} ${bbox[3]})'::box2d`;
|
|
103
173
|
|
|
104
|
-
|
|
174
|
+
const q1 = `SELECT ST_AsMVT(q, 'bi', 4096, 'geom','row') as tile
|
|
105
175
|
FROM (
|
|
106
176
|
SELECT
|
|
107
177
|
floor(random() * 100000 + 1)::int + row_number() over() as row,
|
|
@@ -119,25 +189,25 @@ export default async function clusterVtile(req, reply) {
|
|
|
119
189
|
limit 3000)q
|
|
120
190
|
) q`;
|
|
121
191
|
|
|
122
|
-
|
|
192
|
+
if (query.sql === '2') return q1;
|
|
123
193
|
|
|
124
|
-
|
|
125
|
-
|
|
194
|
+
// auto Index
|
|
195
|
+
funcs.autoIndex({ table, columns: (metrics || []).concat([cluster]) });
|
|
126
196
|
|
|
127
|
-
|
|
197
|
+
const { rows = [] } = await pg.query(q1);
|
|
128
198
|
|
|
129
|
-
|
|
199
|
+
if (query.sql === '3') return rows.map((el) => el.tile);
|
|
130
200
|
|
|
131
|
-
|
|
201
|
+
const buffer = Buffer.concat(rows.map((el) => Buffer.from(el.tile)));
|
|
132
202
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return reply.headers(headers).send(buffer);
|
|
139
|
-
} catch (err) {
|
|
140
|
-
log.error('bi/clusterVtile', { error: err.toString(), query, params });
|
|
141
|
-
return { error: err.toString(), status: 500 };
|
|
203
|
+
if (!nocache) {
|
|
204
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
205
|
+
await writeFile(file, buffer, 'binary');
|
|
142
206
|
}
|
|
143
|
-
|
|
207
|
+
|
|
208
|
+
return reply.headers(headers).send(buffer);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
log.error('bi/clusterVtile', { error: err.toString(), query, params });
|
|
211
|
+
return { error: err.toString(), status: 500 };
|
|
212
|
+
}
|
|
213
|
+
}
|