@opengis/gis 0.2.8 → 0.2.10
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/dist/index.css +1 -1
- package/dist/index.js +2389 -2360
- package/dist/index.umd.cjs +33 -33
- package/module/test/cls/doc_status.json +31 -31
- package/module/test/select/core.user_uid.sql +1 -1
- package/package.json +1 -1
- package/plugin.js +1 -0
- package/server/plugins/crons.js +21 -0
- package/server/routes/map/controllers/vtile.js +22 -16
- package/server/routes/map/vtile1.js +64 -23
- package/utils.js +3 -1
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"id": "1",
|
|
4
|
-
"text": "Діючий",
|
|
5
|
-
"color": "#1ab394"
|
|
6
|
-
},
|
|
7
|
-
{
|
|
8
|
-
"id": "2",
|
|
9
|
-
"text": "Зупинений",
|
|
10
|
-
"color": "#ed82c8"
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
"id": "3",
|
|
14
|
-
"text": "Скасований",
|
|
15
|
-
"color": "#4a4a4a"
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"id": "5",
|
|
19
|
-
"text": "Проектний",
|
|
20
|
-
"color": "#6495ed"
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"id": "7",
|
|
24
|
-
"text": "Очікує розгляду",
|
|
25
|
-
"color": "#92b8ef"
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"id": "10",
|
|
29
|
-
"text": "Архівний",
|
|
30
|
-
"color": "#d48428"
|
|
31
|
-
}
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "1",
|
|
4
|
+
"text": "Діючий",
|
|
5
|
+
"color": "#1ab394"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"id": "2",
|
|
9
|
+
"text": "Зупинений",
|
|
10
|
+
"color": "#ed82c8"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "3",
|
|
14
|
+
"text": "Скасований",
|
|
15
|
+
"color": "#4a4a4a"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "5",
|
|
19
|
+
"text": "Проектний",
|
|
20
|
+
"color": "#6495ed"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "7",
|
|
24
|
+
"text": "Очікує розгляду",
|
|
25
|
+
"color": "#92b8ef"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "10",
|
|
29
|
+
"text": "Архівний",
|
|
30
|
+
"color": "#d48428"
|
|
31
|
+
}
|
|
32
32
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
select uid, coalesce(coalesce(sur_name,'')||coalesce(' '||user_name,'') ||coalesce(' '||father_name,''),login) as text from admin.users
|
|
1
|
+
select uid, coalesce(coalesce(sur_name,'')||coalesce(' '||user_name,'') ||coalesce(' '||father_name,''),login) as text from admin.users
|
package/package.json
CHANGED
package/plugin.js
CHANGED
|
@@ -26,6 +26,7 @@ const columnType = {
|
|
|
26
26
|
async function plugin(app, opts = config) {
|
|
27
27
|
// API
|
|
28
28
|
app.register(import('./server/routes/map/index.mjs'), opts);
|
|
29
|
+
app.register(import('./server/plugins/crons.js'), opts);
|
|
29
30
|
app.register(import('./server/routes/gis/index.mjs'), opts);
|
|
30
31
|
|
|
31
32
|
addHook('afterData', async ({
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { rm } from 'node:fs/promises';
|
|
4
|
+
|
|
5
|
+
import { addCron } from '@opengis/fastify-table/utils.js';
|
|
6
|
+
import rootFolder from './mapnik/funcs/rootFolder.mjs';
|
|
7
|
+
|
|
8
|
+
async function clearOldVectorTiles() {
|
|
9
|
+
if (existsSync(path.join(rootFolder, 'vtile'))) {
|
|
10
|
+
await rm(path.join(rootFolder, 'vtile'), { recursive: true, force: true });
|
|
11
|
+
console.log('clear tiles...', rootFolder, 'vtile');
|
|
12
|
+
return 'vector tiles cleared';
|
|
13
|
+
}
|
|
14
|
+
return 'nothing to clear';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function plugin() {
|
|
18
|
+
addCron(clearOldVectorTiles, 60 * 60 * 24);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default plugin;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
readFile, stat, mkdir, writeFile,
|
|
6
|
+
} from 'node:fs/promises';
|
|
5
7
|
|
|
6
8
|
import Sphericalmercator from '@mapbox/sphericalmercator';
|
|
7
9
|
|
|
8
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
getTemplate, pgClients, getFilterSQL, getMeta, yml2json, getColumnCLS, getSelect,
|
|
12
|
+
} from '@opengis/fastify-table/utils.js';
|
|
9
13
|
|
|
10
14
|
import rootFolder from '../../../plugins/mapnik/funcs/rootFolder.mjs';
|
|
11
15
|
|
|
@@ -14,9 +18,13 @@ const mercator = new Sphericalmercator({ size: 256 });
|
|
|
14
18
|
const types = { point: 'ST_Point', polygon: 'ST_Polygon,ST_MultiPolygon' };
|
|
15
19
|
const systemColumns = ['uid', 'cdate', 'editor_id', 'editor_date', 'created_by', 'created_at', 'updated_by', 'updated_at'];
|
|
16
20
|
|
|
17
|
-
export default async function vtile({
|
|
21
|
+
export default async function vtile({
|
|
22
|
+
pg = pgClients.client, params = {}, query = {}, unittest,
|
|
23
|
+
}, reply) {
|
|
18
24
|
const { id, y, z } = params;
|
|
19
|
-
const {
|
|
25
|
+
const {
|
|
26
|
+
sql, nocache, filter, pointZoom, type, layers,
|
|
27
|
+
} = query;
|
|
20
28
|
const x = params.x?.split?.('.')?.[0] - 0;
|
|
21
29
|
|
|
22
30
|
if (!id) {
|
|
@@ -39,7 +47,7 @@ export default async function vtile({ pg = pgClients.client, params = {}, query
|
|
|
39
47
|
|
|
40
48
|
const filepath = path.join(
|
|
41
49
|
rootFolder,
|
|
42
|
-
`/
|
|
50
|
+
`/vtile/${id}/${hash ? `${createHash('md5').update(hash).digest('hex')}/` : ''}${z}/${x}/${y}.vmt`,
|
|
43
51
|
);
|
|
44
52
|
|
|
45
53
|
if (existsSync(filepath) && z < 13 && !nocache && !sql && !unittest) {
|
|
@@ -57,7 +65,7 @@ export default async function vtile({ pg = pgClients.client, params = {}, query
|
|
|
57
65
|
|
|
58
66
|
const data = await pg.query(
|
|
59
67
|
'select source_path as source, style, geom_type as gt, geometry_column as gc from gis.services where service_id=$1 and is_active and is_public',
|
|
60
|
-
[id]
|
|
68
|
+
[id],
|
|
61
69
|
).then(el => el.rows?.[0]);
|
|
62
70
|
|
|
63
71
|
if (!data) {
|
|
@@ -96,26 +104,24 @@ export default async function vtile({ pg = pgClients.client, params = {}, query
|
|
|
96
104
|
const columnsFiltered = columns
|
|
97
105
|
.filter(el => !['json', 'geometry'].includes(pg.pgType?.[el.dataTypeID]) && !systemColumns.includes(el.name));
|
|
98
106
|
|
|
99
|
-
const props = columnsFiltered.map(el =>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
: `${el.name}${['int4'].includes(el.type) ? '::text' : ''}`;
|
|
103
|
-
});
|
|
107
|
+
const props = columnsFiltered.map(el => (el.type?.[0] === '_'
|
|
108
|
+
? `${el.name}[1] as ${el.name}`
|
|
109
|
+
: `${el.name}${['int4'].includes(el.type) ? '::text' : ''}`));
|
|
104
110
|
|
|
105
111
|
const propsColumns = props.length ? await Promise.all(props.map(async colname => {
|
|
106
112
|
if (!cls?.[colname]) return `"${colname}"`;
|
|
107
|
-
const { type, data } = await pg.queryCache('select type, data from admin.cls where parent is null and name=$1 limit 1', { args: [cls[colname]], table: 'admin.cls' })
|
|
113
|
+
const { type: type2, data: data2 } = await pg.queryCache('select type, data from admin.cls where parent is null and name=$1 limit 1', { args: [cls[colname]], table: 'admin.cls' })
|
|
108
114
|
.then(el => el.rows?.[0] || {}) || {};
|
|
109
115
|
|
|
110
|
-
if (!
|
|
116
|
+
if (!type2 || (type2 === 'sql' && !data2)) {
|
|
111
117
|
return `"${colname}"`;
|
|
112
118
|
}
|
|
113
119
|
|
|
114
|
-
if (
|
|
120
|
+
if (type2 === 'json') {
|
|
115
121
|
return `(select name from admin.cls where parent='${cls[colname]}' and code="${colname}"::text) as "${colname}"`;
|
|
116
122
|
}
|
|
117
123
|
|
|
118
|
-
return `(with c(id,text) as (select * from (${
|
|
124
|
+
return `(with c(id,text) as (select * from (${data2})q limit 1) select text from c where id::text="${colname}"::text) as "${colname}"`;
|
|
119
125
|
})) : '';
|
|
120
126
|
|
|
121
127
|
const q = `SELECT ST_AsMVT(q, '${id.replace(/'/g, "''")}', 4096, 'geom','row') as tile
|
|
@@ -163,4 +169,4 @@ export default async function vtile({ pg = pgClients.client, params = {}, query
|
|
|
163
169
|
}
|
|
164
170
|
|
|
165
171
|
return reply.status(200).headers(headers).send(buffer);
|
|
166
|
-
}
|
|
172
|
+
}
|
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
const headers = {
|
|
2
2
|
'Content-Type': 'application/x-protobuf',
|
|
3
|
-
'Cache-Control': '
|
|
3
|
+
'Cache-Control': '86400',
|
|
4
4
|
};
|
|
5
|
+
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
8
|
+
import { createHash } from 'node:crypto';
|
|
9
|
+
import {
|
|
10
|
+
readFile, stat, mkdir, writeFile,
|
|
11
|
+
} from 'node:fs/promises';
|
|
12
|
+
|
|
5
13
|
import Sphericalmercator from '@mapbox/sphericalmercator';
|
|
6
14
|
|
|
7
15
|
const mercator = new Sphericalmercator({ size: 256 });
|
|
16
|
+
|
|
17
|
+
// import { SphericalMercator } from '@mapbox/sphericalmercator';
|
|
18
|
+
// const mercator = new SphericalMercator({ size: 256 });
|
|
19
|
+
|
|
8
20
|
import {
|
|
9
21
|
getColumnCLS, getTemplate, getMeta, getFilterSQL, getSelect,
|
|
10
22
|
} from '@opengis/fastify-table/utils.js';
|
|
11
23
|
import yaml from 'js-yaml';
|
|
24
|
+
import rootFolder from '../../plugins/mapnik/funcs/rootFolder.mjs';
|
|
12
25
|
|
|
13
26
|
const propsCache = {};
|
|
14
27
|
|
|
@@ -17,6 +30,25 @@ export default async function vtile({
|
|
|
17
30
|
}, reply) {
|
|
18
31
|
const { y, z } = params;
|
|
19
32
|
const x = params.x.split('.')[0] - 0;
|
|
33
|
+
const {
|
|
34
|
+
type, nocache, sql, clusterZoom, filter,
|
|
35
|
+
} = query;
|
|
36
|
+
|
|
37
|
+
const hash = [type, clusterZoom, filter].join();
|
|
38
|
+
|
|
39
|
+
const filepath = path.join(
|
|
40
|
+
rootFolder,
|
|
41
|
+
`/vtile/${params.layer}/${hash ? `${createHash('md5').update(hash).digest('hex')}/` : ''}${z}/${x}/${y}.vmt`,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
if (existsSync(filepath) && +z < 13 && !nocache && !sql && process.env.NODE_ENV !== 'test') {
|
|
45
|
+
const { birthtimeMs = Date.now() } = await stat(filepath);
|
|
46
|
+
const ageInMs = Date.now() - birthtimeMs;
|
|
47
|
+
if (ageInMs < 86400000) {
|
|
48
|
+
const buffer = await readFile(filepath);
|
|
49
|
+
return reply.status(200).headers(headers).send(buffer);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
20
52
|
|
|
21
53
|
// layer
|
|
22
54
|
const [map, layer] = params.layer.includes(':') ? params.layer.split(':') : [null, params.layer];
|
|
@@ -41,7 +73,7 @@ export default async function vtile({
|
|
|
41
73
|
// meta
|
|
42
74
|
const metaTable = await getMeta({ pg, table });
|
|
43
75
|
if (metaTable.error) { return metaTable; }
|
|
44
|
-
const { pk: pkey, columns } = metaTable;
|
|
76
|
+
const { pk: pkey = data.key, columns } = metaTable; // if view - specify via template
|
|
45
77
|
|
|
46
78
|
// props
|
|
47
79
|
|
|
@@ -54,49 +86,51 @@ export default async function vtile({
|
|
|
54
86
|
// console.log(select);
|
|
55
87
|
const clsData = select?.data || col.data;
|
|
56
88
|
const cls = clsData ? await getSelect(clsData).then(el => ({ name: clsData, type: el.arr ? 'cls' : 'select', sql: el.sql })) : null;
|
|
57
|
-
const { name, type, sql } = cls || getColumnCLS(table, col.data || key) || {};
|
|
89
|
+
const { name, type: type1, sql: sql1 } = cls || getColumnCLS(table, col.data || key) || {};
|
|
58
90
|
// console.log({ name, type, sql });
|
|
59
|
-
if (name &&
|
|
91
|
+
if (name && type1 === 'cls') {
|
|
60
92
|
return `(select name from admin.cls where parent='${name}' and code::text=${key}::text limit 1) as "${key}_text","${key}"::text`;
|
|
61
93
|
}
|
|
62
|
-
if (
|
|
63
|
-
const { fields = [] } = await pg.query(`${
|
|
64
|
-
return `(select "${fields[1].name}" from (${
|
|
94
|
+
if (type1 === 'select' && sql1) {
|
|
95
|
+
const { fields = [] } = await pg.query(`${sql1} limit 0`);
|
|
96
|
+
return `(select "${fields[1].name}" from (${sql1})s${idx} where "${fields[0].name}"::text="${key}"::text limit 1) as "${key}_text","${key}"::text`;
|
|
65
97
|
}
|
|
66
98
|
return `"${key}"::text`;
|
|
67
99
|
}));
|
|
68
100
|
propsCache[cache] = propsCache[cache] || propsWithCls;
|
|
69
101
|
|
|
70
102
|
const geomCol = (style?.type === 'point' ? `ST_Centroid(${geom})` : null)
|
|
71
|
-
|
|
103
|
+
|| (parseInt(z, 10) < parseInt(query.pointZoom || style?.pointZoom || style?.iconZoom || '0', 10) ? `ST_Centroid(${geom})` : geom);
|
|
72
104
|
|
|
73
105
|
const koef = {
|
|
74
106
|
10: 0.1, 11: 0.05, 12: 0.005, 13: 0.001, 14: 0.001, 15: 0.0005,
|
|
75
107
|
}[z] || 0.0005;
|
|
76
108
|
|
|
77
|
-
const { type } = query;
|
|
78
109
|
const types = { point: 'ST_Point' /* ,ST_MultiPoint */, polygon: 'ST_Polygon,ST_MultiPolygon' };
|
|
79
|
-
const
|
|
80
|
-
|
|
110
|
+
const queryMode = ((clusterZoom - z) > 2 ? 1 : null) || ((clusterZoom - z) > 0 ? 2 : null) || 3;
|
|
111
|
+
// console.log(z, clusterZoom, queryMode);
|
|
112
|
+
const props1 = props.filter((el, idx, arr) => idx === arr.findIndex(item => item.name === el.name));
|
|
113
|
+
const q = {
|
|
114
|
+
1: `SELECT ST_AsMVT(q, '${layer}', 4096, 'geom','row') as tile
|
|
81
115
|
FROM (
|
|
82
116
|
SELECT floor(random() * 100000 + 1)::int + row_number() over() as row, point_count, ST_AsMVTGeom(st_transform(geom,3857),ST_TileEnvelope(${z},${y},${x})::box2d,4096,256,false) geom
|
|
83
117
|
FROM (
|
|
84
118
|
SELECT st_centroid(st_union(geom)) as geom, count(*) as point_count
|
|
85
119
|
FROM ${table} where ${layerQuery || 'true'} and ${filterData?.q || 'true'} and ${geom} && ${bbox2d} ) j
|
|
86
|
-
)q
|
|
87
|
-
|
|
88
|
-
? `SELECT ST_AsMVT(q, '${layer}', 4096, 'geom','row') as tile
|
|
120
|
+
)q`,
|
|
121
|
+
2: `SELECT ST_AsMVT(q, '${layer}', 4096, 'geom','row') as tile
|
|
89
122
|
FROM (
|
|
90
|
-
SELECT floor(random() * 100000 + 1)::int +row_number() over() as row, count(*) as point_count,
|
|
123
|
+
SELECT floor(random() * 100000 + 1)::int +row_number() over() as row, count(*) as point_count,
|
|
124
|
+
${props?.length ? `${props1.map(prop => `case when count(*)=1 then min(${prop.name.replace(/'/g, "''")}) else null end as "${prop.name.replace(/'/g, "''")}"`).join(',')},` : ''}
|
|
125
|
+
ST_AsMVTGeom(st_transform(st_centroid(ST_Union(geom)),3857),ST_TileEnvelope(${z},${y},${x})::box2d,4096,256,false) as geom
|
|
91
126
|
FROM (
|
|
92
|
-
SELECT geom, ST_ClusterDBSCAN(geom,${koef},1) OVER () AS cluster
|
|
127
|
+
SELECT ${props?.length ? `${props1.map(prop => prop.name).join(',')},` : ''} geom, ST_ClusterDBSCAN(geom,${koef},1) OVER () AS cluster
|
|
93
128
|
FROM ${table} where ${layerQuery || 'true'} and ${filterData?.q || 'true'} and ${geom} && ${bbox2d} ) j
|
|
94
129
|
WHERE cluster IS NOT NULL
|
|
95
130
|
GROUP BY cluster
|
|
96
131
|
ORDER BY 1 DESC
|
|
97
|
-
)q
|
|
98
|
-
|
|
99
|
-
: `SELECT ST_AsMVT(q, '${layer}', 4096, 'geom','row') as tile
|
|
132
|
+
)q`,
|
|
133
|
+
3: `SELECT ST_AsMVT(q, '${layer}', 4096, 'geom','row') as tile
|
|
100
134
|
FROM (
|
|
101
135
|
SELECT
|
|
102
136
|
|
|
@@ -120,10 +154,17 @@ export default async function vtile({
|
|
|
120
154
|
${(style?.type || data?.geometry_type) === 'polygon' ? `order by st_area(st_transform(${geom},3857)) desc` : ''}
|
|
121
155
|
|
|
122
156
|
limit 3000)q
|
|
123
|
-
) q
|
|
157
|
+
) q`,
|
|
158
|
+
}[queryMode];
|
|
124
159
|
|
|
125
|
-
if (query.sql === '1') { return
|
|
126
|
-
const tile = await pg.query(
|
|
160
|
+
if (query.sql === '1') { return q; }
|
|
161
|
+
const tile = await pg.query(q);
|
|
127
162
|
const buffer = Buffer.concat(tile.rows.map((el) => Buffer.from(el.tile)));
|
|
128
|
-
|
|
163
|
+
|
|
164
|
+
if (!nocache) {
|
|
165
|
+
await mkdir(path.dirname(filepath), { recursive: true });
|
|
166
|
+
await writeFile(filepath, buffer, 'binary');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return reply.headers({ ...headers, 'Cache-Control': nocache ? 'no-cache' : headers['Cache-Control'] }).send(buffer);
|
|
129
170
|
}
|
package/utils.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import gisRegistry from './server/routes/gis/registers/gis.registry.js';
|
|
2
2
|
import gisRegistryList from './server/routes/gis/registers/gis.registry.list.js';
|
|
3
3
|
import mapRegistry from './server/routes/gis/registers/map.registry.js';
|
|
4
|
+
import rootFolder from './server/plugins/mapnik/funcs/rootFolder.mjs';
|
|
4
5
|
|
|
5
6
|
export {
|
|
6
7
|
gisRegistry,
|
|
7
8
|
gisRegistryList,
|
|
8
|
-
mapRegistry
|
|
9
|
+
mapRegistry,
|
|
10
|
+
rootFolder,
|
|
9
11
|
};
|