@opengis/bi 1.0.14 → 1.0.15
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 +50 -50
- package/config.js +12 -12
- package/dist/bi.js +1 -1
- package/dist/bi.umd.cjs +63 -63
- package/dist/{import-file-DUp3rsNI.js → import-file-CRC0sYYT.js} +8055 -7987
- package/dist/{map-component-mixin-CGM0P5ub.js → map-component-mixin-BCtWEvzv.js} +3795 -2116
- package/dist/style.css +1 -1
- package/dist/{vs-calendar-cOoinEwc.js → vs-calendar-5ot79n0N.js} +20 -9
- package/dist/{vs-funnel-bar-kLkPoIhJ.js → vs-funnel-bar-CLo6gXI_.js} +2 -2
- package/dist/{vs-heatmap-3XAVGTSo.js → vs-heatmap-DHGA8dRk.js} +3 -4
- package/dist/{vs-map-cluster-BWJPx7wE.js → vs-map-cluster-CNgX6JVF.js} +2 -2
- package/dist/{vs-map-B1tr6V5_.js → vs-map-pIn5wS4G.js} +2 -2
- package/dist/{vs-number-CrU7LmkV.js → vs-number-DYfok8VU.js} +19 -12
- package/dist/{vs-text-DRPx3aID.js → vs-text-Dckykz09.js} +19 -14
- package/package.json +107 -97
- package/plugin.js +14 -13
- package/server/migrations/bi.dataset.sql +26 -0
- package/server/migrations/bi.sql +93 -93
- package/server/plugins/docs.js +48 -48
- package/server/plugins/hook.js +89 -89
- package/server/plugins/vite.js +69 -69
- package/server/routes/dashboard/controllers/dashboard.delete.js +38 -37
- package/server/routes/dashboard/controllers/dashboard.js +118 -114
- package/server/routes/dashboard/controllers/dashboard.list.js +30 -36
- package/server/routes/dashboard/controllers/utils/yaml.js +11 -11
- package/server/routes/dashboard/index.mjs +25 -25
- package/server/routes/data/controllers/data.js +167 -156
- package/server/routes/data/controllers/util/chartSQL.js +42 -39
- package/server/routes/data/controllers/util/normalizeData.js +59 -56
- package/server/routes/data/index.mjs +29 -24
- package/server/routes/dataset/controllers/bi.dataset.demo.add.js +97 -0
- package/server/routes/dataset/controllers/bi.dataset.import.js +67 -0
- package/server/routes/dataset/controllers/util/create.table.js +22 -0
- package/server/routes/dataset/controllers/util/prepare.data.js +49 -0
- package/server/routes/dataset/index.mjs +19 -0
- package/server/routes/db/controllers/dbTablePreview.js +63 -63
- package/server/routes/db/controllers/dbTables.js +36 -36
- package/server/routes/db/index.mjs +17 -17
- package/server/routes/edit/controllers/dashboard.add.js +26 -24
- package/server/routes/edit/controllers/dashboard.edit.js +46 -44
- package/server/routes/edit/controllers/widget.add.js +75 -73
- package/server/routes/edit/controllers/widget.del.js +69 -70
- package/server/routes/edit/controllers/widget.edit.js +52 -103
- package/server/routes/edit/index.mjs +31 -31
- package/server/routes/map/controllers/cluster.js +109 -104
- package/server/routes/map/controllers/clusterVtile.js +166 -213
- package/server/routes/map/controllers/geojson.js +127 -127
- package/server/routes/map/controllers/map.js +60 -57
- package/server/routes/map/controllers/utils/downloadClusterData.js +43 -0
- package/server/routes/map/controllers/vtile.js +183 -182
- package/server/routes/map/index.mjs +25 -25
- package/server/utils/getWidget.js +85 -83
- package/utils.js +12 -12
|
@@ -1,104 +1,109 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
1
|
+
import { getFilterSQL } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
|
|
3
|
+
import { getWidget } from '../../../../utils.js';
|
|
4
|
+
|
|
5
|
+
import downloadClusterData from './utils/downloadClusterData.js';
|
|
6
|
+
|
|
7
|
+
const clusterExists = {};
|
|
8
|
+
|
|
9
|
+
export default async function cluster({ pg, query = {}, log }) {
|
|
10
|
+
const { widget, filter, dashboard, search } = query;
|
|
11
|
+
|
|
12
|
+
if (!widget) {
|
|
13
|
+
return { message: 'not enough params: widget', status: 400 };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { data } = await getWidget({ dashboard, widget });
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const pkey = pg.pk?.[data?.table];
|
|
20
|
+
|
|
21
|
+
if (!pkey) {
|
|
22
|
+
return {
|
|
23
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: table pk not found (${data?.table})`,
|
|
24
|
+
status: 400,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// data param
|
|
29
|
+
const {
|
|
30
|
+
table,
|
|
31
|
+
query: where = '1=1',
|
|
32
|
+
metrics = [],
|
|
33
|
+
cluster,
|
|
34
|
+
clusterTable = {},
|
|
35
|
+
} = data;
|
|
36
|
+
|
|
37
|
+
if (!cluster) {
|
|
38
|
+
return {
|
|
39
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: cluster column not specified`,
|
|
40
|
+
status: 400,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!metrics.length) {
|
|
45
|
+
return {
|
|
46
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: metric columns not found`,
|
|
47
|
+
status: 400,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!clusterTable?.name) {
|
|
52
|
+
Object.assign(clusterTable, {
|
|
53
|
+
name: 'bi.cluster',
|
|
54
|
+
title: 'title',
|
|
55
|
+
query: `type='${cluster}'`,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (cluster && !clusterExists[cluster]) {
|
|
60
|
+
const res = await downloadClusterData({ pg, log, cluster });
|
|
61
|
+
if (res) return res;
|
|
62
|
+
clusterExists[cluster] = 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (clusterTable?.name && !pg.pk?.[clusterTable?.name]) {
|
|
66
|
+
return {
|
|
67
|
+
message: 'invalid widget params: clusterTable pkey not found',
|
|
68
|
+
status: 404,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const { bounds, extentStr } = await pg.query(`select count(*),
|
|
73
|
+
st_asgeojson(st_extent(geom))::json as bounds,
|
|
74
|
+
replace(regexp_replace(st_extent(geom)::box2d::text,'BOX\\(|\\)','','g'),' ',',') as "extentStr"
|
|
75
|
+
from ${table} where ${where || '1=1'}`).then((res) => res.rows?.[0] || {});
|
|
76
|
+
const extent = extentStr ? extentStr.split(',') : undefined;
|
|
77
|
+
|
|
78
|
+
// get sql
|
|
79
|
+
const { optimizedSQL } =
|
|
80
|
+
filter || search
|
|
81
|
+
? await getFilterSQL({ pg, table, filter, search })
|
|
82
|
+
: {};
|
|
83
|
+
|
|
84
|
+
const q = `select "${cluster}" as name, sum("${metrics[0]}")::float as metric
|
|
85
|
+
from ${optimizedSQL ? `(${optimizedSQL})` : table} q
|
|
86
|
+
left join lateral (select "${pg.pk?.[clusterTable?.name]}" as id from ${clusterTable?.name} where ${clusterTable?.title}=q."${cluster}" limit 1)b on 1=1
|
|
87
|
+
where ${where} group by ${cluster}, b.id order by sum("${metrics[0]}")::float desc`;
|
|
88
|
+
|
|
89
|
+
if (query.sql === '1') return q;
|
|
90
|
+
|
|
91
|
+
// auto Index
|
|
92
|
+
// autoIndex({ table, columns: (metrics || []).concat([cluster]) });
|
|
93
|
+
|
|
94
|
+
const { rows = [] } = await pg.query(q);
|
|
95
|
+
const vals = rows.map((el) => el.metric - 0).sort((a, b) => a - b);
|
|
96
|
+
const len = vals.length;
|
|
97
|
+
const sizes = [
|
|
98
|
+
vals[0],
|
|
99
|
+
vals[Math.floor(len / 4)],
|
|
100
|
+
vals[Math.floor(len / 2)],
|
|
101
|
+
vals[Math.floor(len * 0.75)],
|
|
102
|
+
vals[len - 1],
|
|
103
|
+
];
|
|
104
|
+
return { sizes, rows, bounds, extent, count: rows.length, total: rows?.reduce((acc, curr) => (curr.metric || 0) + acc, 0) };
|
|
105
|
+
} catch (err) {
|
|
106
|
+
log.error('bi/cluster', { error: err.toString(), query });
|
|
107
|
+
return { error: err.toString(), status: 500 };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -1,213 +1,166 @@
|
|
|
1
|
-
import Sphericalmercator from '@mapbox/sphericalmercator';
|
|
2
|
-
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { createHash } from 'crypto';
|
|
5
|
-
import { writeFile, mkdir } from 'fs/promises';
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
parseInt(z, 10) < parseInt(pointZoom, 10)
|
|
168
|
-
? `ST_Centroid(${clusterTable?.geom || data?.geom || 'geom'})`
|
|
169
|
-
: clusterTable?.geom || data?.geom || 'geom';
|
|
170
|
-
|
|
171
|
-
const bbox = mercator.bbox(+y, +x, +z, false /* , '900913' */);
|
|
172
|
-
const bbox2d = `'BOX(${bbox[0]} ${bbox[1]},${bbox[2]} ${bbox[3]})'::box2d`;
|
|
173
|
-
|
|
174
|
-
const q1 = `SELECT ST_AsMVT(q, 'bi', 4096, 'geom','row') as tile
|
|
175
|
-
FROM (
|
|
176
|
-
SELECT
|
|
177
|
-
floor(random() * 100000 + 1)::int + row_number() over() as row,
|
|
178
|
-
|
|
179
|
-
${pg.pk?.[clusterTable?.name] ? 'id,' : ''} name, metric,
|
|
180
|
-
|
|
181
|
-
ST_AsMVTGeom(st_transform(${geomCol}, 3857),ST_TileEnvelope(${z},${y},${x})::box2d,4096,256,false) as geom
|
|
182
|
-
|
|
183
|
-
FROM (select * from (${q})q where geom && ${bbox2d}
|
|
184
|
-
|
|
185
|
-
and geom is not null and st_srid(geom) >0
|
|
186
|
-
|
|
187
|
-
and ST_GeometryType(geom) = any ('{ "ST_Polygon", "ST_MultiPolygon" }')
|
|
188
|
-
|
|
189
|
-
limit 3000)q
|
|
190
|
-
) q`;
|
|
191
|
-
|
|
192
|
-
if (query.sql === '2') return q1;
|
|
193
|
-
|
|
194
|
-
// auto Index
|
|
195
|
-
funcs.autoIndex({ table, columns: (metrics || []).concat([cluster]) });
|
|
196
|
-
|
|
197
|
-
const { rows = [] } = await pg.query(q1);
|
|
198
|
-
|
|
199
|
-
if (query.sql === '3') return rows.map((el) => el.tile);
|
|
200
|
-
|
|
201
|
-
const buffer = Buffer.concat(rows.map((el) => Buffer.from(el.tile)));
|
|
202
|
-
|
|
203
|
-
if (!nocache) {
|
|
204
|
-
await mkdir(path.dirname(file), { recursive: true });
|
|
205
|
-
await writeFile(file, buffer, 'binary');
|
|
206
|
-
}
|
|
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
|
-
}
|
|
1
|
+
import Sphericalmercator from '@mapbox/sphericalmercator';
|
|
2
|
+
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { createHash } from 'crypto';
|
|
5
|
+
import { writeFile, mkdir } from 'fs/promises';
|
|
6
|
+
|
|
7
|
+
import { getFolder, getFilterSQL, autoIndex } from '@opengis/fastify-table/utils.js';
|
|
8
|
+
|
|
9
|
+
import { getWidget } from '../../../../utils.js';
|
|
10
|
+
|
|
11
|
+
import downloadClusterData from './utils/downloadClusterData.js';
|
|
12
|
+
|
|
13
|
+
const mercator = new Sphericalmercator({ size: 256 });
|
|
14
|
+
|
|
15
|
+
const clusterExists = {};
|
|
16
|
+
|
|
17
|
+
export default async function clusterVtile(req, reply) {
|
|
18
|
+
const { pg, funcs, params = {}, query = {}, log } = req;
|
|
19
|
+
const { z, y } = params;
|
|
20
|
+
const x = params.x?.split('.')[0] - 0;
|
|
21
|
+
|
|
22
|
+
if (!x || !y || !z) {
|
|
23
|
+
return { message: 'not enough params: xyz', status: 400 };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { widget, filter, dashboard, search, clusterZoom, nocache, pointZoom } =
|
|
27
|
+
query;
|
|
28
|
+
|
|
29
|
+
if (!widget) {
|
|
30
|
+
return { message: 'not enough params: widget', status: 400 };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { data } = await getWidget({ dashboard, widget });
|
|
34
|
+
|
|
35
|
+
const headers = {
|
|
36
|
+
'Content-Type': 'application/x-protobuf',
|
|
37
|
+
'Cache-Control':
|
|
38
|
+
nocache || query.sql ? 'no-cache' : 'public, max-age=86400',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const hash = [pointZoom, filter].filter((el) => el).join();
|
|
42
|
+
|
|
43
|
+
const root = getFolder(req);
|
|
44
|
+
const file = path.join(
|
|
45
|
+
root,
|
|
46
|
+
`/map/vtile/${widget}/${hash ? `${createHash('sha1').update(hash).digest('base64')}/` : ''}${z}/${x}/${y}.mvt`
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
if (!data?.table) {
|
|
51
|
+
return {
|
|
52
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: table not specified`,
|
|
53
|
+
status: 400,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const pkey = pg.pk?.[data?.table];
|
|
58
|
+
|
|
59
|
+
if (!pkey) {
|
|
60
|
+
return {
|
|
61
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: table pk not found (${data?.table})`,
|
|
62
|
+
status: 400,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// data param
|
|
67
|
+
const {
|
|
68
|
+
table,
|
|
69
|
+
query: where = '1=1',
|
|
70
|
+
metrics = [],
|
|
71
|
+
cluster,
|
|
72
|
+
clusterTable = {},
|
|
73
|
+
} = data;
|
|
74
|
+
if (!clusterTable?.name) {
|
|
75
|
+
Object.assign(clusterTable, {
|
|
76
|
+
name: 'bi.cluster',
|
|
77
|
+
title: 'title',
|
|
78
|
+
query: `type='${data.cluster}'`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (cluster && !clusterExists[data.cluster]) {
|
|
83
|
+
const res = await downloadClusterData({ pg, log, cluster });
|
|
84
|
+
if (res) return res;
|
|
85
|
+
clusterExists[cluster] = 1;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!cluster) {
|
|
89
|
+
return {
|
|
90
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: cluster column not specified`,
|
|
91
|
+
status: 400,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!metrics.length) {
|
|
96
|
+
return {
|
|
97
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: metric columns not found`,
|
|
98
|
+
status: 400,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// get sql
|
|
103
|
+
const { optimizedSQL } =
|
|
104
|
+
filter || search
|
|
105
|
+
? await getFilterSQL({ pg, table, filter, search })
|
|
106
|
+
: {};
|
|
107
|
+
|
|
108
|
+
const q = `select "${cluster}" as name, sum("${metrics[0]}")::float as metric, b.*
|
|
109
|
+
from ${optimizedSQL ? `(${optimizedSQL})` : table} q
|
|
110
|
+
left join lateral (select "${pg.pk?.[clusterTable?.name]}" as id,
|
|
111
|
+
${clusterTable?.geom || 'geom'} as geom from ${clusterTable?.name}
|
|
112
|
+
where ${clusterTable?.query || '1=1'} and ${clusterTable?.title}=q."${cluster}" limit 1
|
|
113
|
+
)b on 1=1
|
|
114
|
+
where ${where} group by
|
|
115
|
+
${cluster}, b.id, b.${clusterTable?.geom || 'geom'}`;
|
|
116
|
+
|
|
117
|
+
if (query.sql === '1') return q;
|
|
118
|
+
|
|
119
|
+
const geomCol =
|
|
120
|
+
parseInt(z, 10) < parseInt(pointZoom, 10)
|
|
121
|
+
? `ST_Centroid(${clusterTable?.geom || data?.geom || 'geom'})`
|
|
122
|
+
: clusterTable?.geom || data?.geom || 'geom';
|
|
123
|
+
|
|
124
|
+
const bbox = mercator.bbox(+y, +x, +z, false /* , '900913' */);
|
|
125
|
+
const bbox2d = `'BOX(${bbox[0]} ${bbox[1]},${bbox[2]} ${bbox[3]})'::box2d`;
|
|
126
|
+
|
|
127
|
+
const q1 = `SELECT ST_AsMVT(q, 'bi', 4096, 'geom','row') as tile
|
|
128
|
+
FROM (
|
|
129
|
+
SELECT
|
|
130
|
+
floor(random() * 100000 + 1)::int + row_number() over() as row,
|
|
131
|
+
|
|
132
|
+
${pg.pk?.[clusterTable?.name] ? 'id,' : ''} name, metric,
|
|
133
|
+
|
|
134
|
+
ST_AsMVTGeom(st_transform(${geomCol}, 3857),ST_TileEnvelope(${z},${y},${x})::box2d,4096,256,false) as geom
|
|
135
|
+
|
|
136
|
+
FROM (select * from (${q})q where geom && ${bbox2d}
|
|
137
|
+
|
|
138
|
+
and geom is not null and st_srid(geom) >0
|
|
139
|
+
|
|
140
|
+
and ST_GeometryType(geom) = any ('{ "ST_Polygon", "ST_MultiPolygon" }')
|
|
141
|
+
|
|
142
|
+
limit 3000)q
|
|
143
|
+
) q`;
|
|
144
|
+
|
|
145
|
+
if (query.sql === '2') return q1;
|
|
146
|
+
|
|
147
|
+
// auto Index
|
|
148
|
+
autoIndex({ table, columns: (metrics || []).concat([cluster]) });
|
|
149
|
+
|
|
150
|
+
const { rows = [] } = await pg.query(q1);
|
|
151
|
+
|
|
152
|
+
if (query.sql === '3') return rows.map((el) => el.tile);
|
|
153
|
+
|
|
154
|
+
const buffer = Buffer.concat(rows.map((el) => Buffer.from(el.tile)));
|
|
155
|
+
|
|
156
|
+
if (!nocache) {
|
|
157
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
158
|
+
await writeFile(file, buffer, 'binary');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return reply.headers(headers).send(buffer);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
log.error('bi/clusterVtile', { error: err.toString(), query, params });
|
|
164
|
+
return { error: err.toString(), status: 500 };
|
|
165
|
+
}
|
|
166
|
+
}
|