@opengis/gis 0.2.76 → 0.2.78
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 +5955 -5648
- package/dist/index.umd.cjs +59 -59
- package/module/gis/form/gis.cartocss.form.json +6 -12
- package/module/gis/table/gis.cartocss.table.json +9 -2
- package/package.json +4 -4
- package/server/migrations/cartocss.sql +1 -0
- package/server/plugins/mapnik/map.proto +41 -0
- package/server/routes/gis/cartocss/add.cartocss.js +23 -14
- package/server/routes/gis/cartocss/get.cartocss.js +1 -1
- package/server/routes/map/controllers/layerList.js +6 -1
- package/server/routes/mapnik/controllers/clearTiles.js +94 -0
- package/server/routes/mapnik/controllers/createXml.js +4 -1
- package/server/routes/mapnik/controllers/fileSearch.js +34 -0
- package/server/routes/mapnik/controllers/fileStat.js +27 -0
- package/server/routes/mapnik/controllers/rasterInfo.js +11 -3
- package/server/routes/mapnik/controllers/readDir.js +19 -0
- package/server/routes/mapnik/controllers/rtile.js +33 -10
- package/server/routes/mapnik/functions/cartoBounds.js +1 -0
- package/server/routes/mapnik/functions/uploadXML.js +10 -6
- package/server/routes/mapnik/index.js +8 -0
|
@@ -11,24 +11,18 @@
|
|
|
11
11
|
"group_id": {
|
|
12
12
|
"type": "Autocomplete",
|
|
13
13
|
"data": "gis.group_list",
|
|
14
|
-
"col":
|
|
14
|
+
"col": 12,
|
|
15
15
|
"ua": "Група"
|
|
16
16
|
},
|
|
17
|
-
"
|
|
18
|
-
"ua": "
|
|
19
|
-
"col":
|
|
20
|
-
"type": "Text"
|
|
21
|
-
"validators": [
|
|
22
|
-
"required"
|
|
23
|
-
]
|
|
17
|
+
"source_path": {
|
|
18
|
+
"ua": "Path",
|
|
19
|
+
"col": 12,
|
|
20
|
+
"type": "Text"
|
|
24
21
|
},
|
|
25
22
|
"description": {
|
|
26
23
|
"ua": "Опис",
|
|
27
24
|
"col": 12,
|
|
28
|
-
"type": "TextArea"
|
|
29
|
-
"validators": [
|
|
30
|
-
"required"
|
|
31
|
-
]
|
|
25
|
+
"type": "TextArea"
|
|
32
26
|
},
|
|
33
27
|
"is_public": {
|
|
34
28
|
"ua": "Чи є карта публічною?",
|
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
"format": "text",
|
|
16
16
|
"link": "/gis.cartocss/{id}"
|
|
17
17
|
},
|
|
18
|
+
{
|
|
19
|
+
"ua": "Шлях",
|
|
20
|
+
"name": "source_path",
|
|
21
|
+
"format": "text"
|
|
22
|
+
},
|
|
18
23
|
{
|
|
19
24
|
"ua": "Опис",
|
|
20
25
|
"name": "description",
|
|
@@ -30,13 +35,15 @@
|
|
|
30
35
|
"ua": "Включений",
|
|
31
36
|
"name": "enabled",
|
|
32
37
|
"data": "yes_no",
|
|
33
|
-
"format": "
|
|
38
|
+
"format": "boolean",
|
|
39
|
+
"edit": true
|
|
34
40
|
},
|
|
35
41
|
{
|
|
36
42
|
"ua": "Публічний",
|
|
37
43
|
"name": "is_public",
|
|
38
44
|
"data": "yes_no",
|
|
39
|
-
"format": "
|
|
45
|
+
"format": "boolean",
|
|
46
|
+
"edit": true
|
|
40
47
|
}
|
|
41
48
|
],
|
|
42
49
|
"filterList": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengis/gis",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.78",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "Softpro",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -52,12 +52,12 @@
|
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@opengis/core": "^0.0.23",
|
|
55
|
-
"@opengis/fastify-table": "^2.0.
|
|
55
|
+
"@opengis/fastify-table": "^2.0.132",
|
|
56
56
|
"@opengis/filter": "0.1.31",
|
|
57
57
|
"@opengis/form": "^0.0.103",
|
|
58
58
|
"@opengis/table": "^0.0.27",
|
|
59
59
|
"@vitejs/plugin-vue": "^5.2.4",
|
|
60
|
-
"axios": "^1.13.
|
|
60
|
+
"axios": "^1.13.5",
|
|
61
61
|
"eslint": "^8.57.1",
|
|
62
62
|
"eslint-config-airbnb": "19.0.4",
|
|
63
63
|
"eslint-plugin-import": "^2.32.0",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"sass-embedded": "1.86.3",
|
|
69
69
|
"typescript": "^5.9.3",
|
|
70
70
|
"vite": "^6.4.1",
|
|
71
|
-
"vue": "^3.5.
|
|
71
|
+
"vue": "^3.5.28",
|
|
72
72
|
"vue-router": "4.5.1",
|
|
73
73
|
"vuedraggable": "^4.1.0"
|
|
74
74
|
}
|
|
@@ -11,6 +11,8 @@ service MapService {
|
|
|
11
11
|
rpc RenderTile(RenderTileRequest) returns (RenderTileOut);
|
|
12
12
|
rpc ClearTile(ClearTileRequest) returns (ClearTileOut);
|
|
13
13
|
rpc ReadDir(ReadDirRequest) returns (ReadDirOut);
|
|
14
|
+
rpc FileStat(FileStatRequest) returns (FileStatOut);
|
|
15
|
+
rpc FileSearch(FileSearchRequest) returns (FileSearchOut);
|
|
14
16
|
rpc CreateXML(CreateXMLRequest) returns (stream JobProgress); // streaming XML creation
|
|
15
17
|
rpc UploadXML(UploadXMLRequest) returns (UploadXMLOut);
|
|
16
18
|
rpc UploadRaster(UploadRasterRequest) returns (UploadRasterOut);
|
|
@@ -50,6 +52,33 @@ message ClearTileOut {
|
|
|
50
52
|
string err = 2;
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
message FileStatRequest {
|
|
56
|
+
string path = 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
message FileStatOut {
|
|
60
|
+
bool exists = 1;
|
|
61
|
+
string name = 2;
|
|
62
|
+
int64 size = 3;
|
|
63
|
+
string adate = 4;
|
|
64
|
+
string cdate = 5;
|
|
65
|
+
string mdate = 6;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
message FileSearchRequest {
|
|
69
|
+
string path = 1;
|
|
70
|
+
string key = 2;
|
|
71
|
+
string period = 3;
|
|
72
|
+
int64 size = 4;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
message FileSearchOut {
|
|
76
|
+
bool exists = 1;
|
|
77
|
+
int32 count = 2;
|
|
78
|
+
repeated FileInfo files = 3;
|
|
79
|
+
repeated DirInfo directories = 4;
|
|
80
|
+
}
|
|
81
|
+
|
|
53
82
|
message ReadDirRequest {
|
|
54
83
|
string path = 1;
|
|
55
84
|
}
|
|
@@ -58,12 +87,21 @@ message FileInfo {
|
|
|
58
87
|
string name = 1;
|
|
59
88
|
int64 size = 2;
|
|
60
89
|
string date = 3;
|
|
90
|
+
string dir = 4;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
message DirInfo {
|
|
94
|
+
string name = 1;
|
|
95
|
+
int64 size = 2;
|
|
96
|
+
string date = 3;
|
|
97
|
+
int64 children = 4;
|
|
61
98
|
}
|
|
62
99
|
|
|
63
100
|
message ReadDirOut {
|
|
64
101
|
bool exists = 1;
|
|
65
102
|
int32 count = 2;
|
|
66
103
|
repeated FileInfo files = 3;
|
|
104
|
+
repeated DirInfo directories = 4;
|
|
67
105
|
}
|
|
68
106
|
|
|
69
107
|
message CreateXMLRequest {
|
|
@@ -75,12 +113,14 @@ message CreateXMLRequest {
|
|
|
75
113
|
string name = 6;
|
|
76
114
|
string debug = 7;
|
|
77
115
|
string nocache = 8;
|
|
116
|
+
string proj4 = 9;
|
|
78
117
|
}
|
|
79
118
|
|
|
80
119
|
message UploadXMLRequest {
|
|
81
120
|
string name = 1;
|
|
82
121
|
string xml = 2;
|
|
83
122
|
string encoding = 3;
|
|
123
|
+
string path = 4;
|
|
84
124
|
}
|
|
85
125
|
|
|
86
126
|
message UploadXMLOut {
|
|
@@ -144,6 +184,7 @@ message GetRasterInfoRequest {
|
|
|
144
184
|
string path = 1;
|
|
145
185
|
string ttl = 2;
|
|
146
186
|
int64 srid = 3;
|
|
187
|
+
string proj4 = 4;
|
|
147
188
|
}
|
|
148
189
|
|
|
149
190
|
message FileItem {
|
|
@@ -1,29 +1,38 @@
|
|
|
1
|
-
import { dataUpdate } from '@opengis/fastify-table/utils.js';
|
|
1
|
+
import { pgClients, dataUpdate } from '@opengis/fastify-table/utils.js';
|
|
2
2
|
|
|
3
3
|
import uploadXML from '../../mapnik/functions/uploadXML.js';
|
|
4
4
|
|
|
5
5
|
export default async function addCartocss(req, reply) {
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
pg = pgClients.client, body, params, user,
|
|
8
|
+
} = req;
|
|
7
9
|
|
|
8
|
-
const
|
|
9
|
-
if (!id) return reply.status(400).send({ message: 'id is empty', status: 400 });
|
|
10
|
+
const cartocss = await pg.query('select cartocss_id, source_path, config, style from gis.cartocss where cartocss_id=$1', [params.id]).then(el => el.rows?.[0]);
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
if (!cartocss) {
|
|
13
|
+
return reply.status(404).send({ error: 'cartocss not found', code: 404 });
|
|
14
|
+
}
|
|
13
15
|
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
16
|
+
const sourcePath = body.source_path || cartocss.source_path;
|
|
17
|
+
const config = body.config || cartocss.config;
|
|
18
|
+
const style = body.style || cartocss.style;
|
|
19
|
+
|
|
20
|
+
const data = { ...body, config };
|
|
17
21
|
|
|
18
22
|
await dataUpdate({
|
|
19
23
|
pg,
|
|
20
|
-
id,
|
|
24
|
+
id: params.id,
|
|
21
25
|
table: 'gis.cartocss',
|
|
22
|
-
data
|
|
23
|
-
uid:
|
|
26
|
+
data,
|
|
27
|
+
uid: user?.uid,
|
|
24
28
|
});
|
|
25
29
|
|
|
26
|
-
await uploadXML({
|
|
30
|
+
await uploadXML({
|
|
31
|
+
id: params.id,
|
|
32
|
+
dataset: config,
|
|
33
|
+
style,
|
|
34
|
+
source_path: sourcePath,
|
|
35
|
+
}, pg);
|
|
27
36
|
|
|
28
|
-
return reply.status(200).send({ message: 'cartocss updated',
|
|
37
|
+
return reply.status(200).send({ message: 'cartocss updated', code: 200 });
|
|
29
38
|
}
|
|
@@ -15,7 +15,7 @@ export default async function checkCarto({
|
|
|
15
15
|
return reply.status(400).send({ error: 'mapnik server address needed', code: 400 });
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const cartocss = await pg.query('select config, ARRAY[ST_XMin(geom), ST_YMin(geom), ST_XMax(geom), ST_YMax(geom)] as bounds, cartocss_key,name,description,style,group_id,enabled,is_public from gis.cartocss where cartocss_id=$1', [params.id]).then(el => el.rows?.[0]);
|
|
18
|
+
const cartocss = await pg.query('select config, ARRAY[ST_XMin(geom), ST_YMin(geom), ST_XMax(geom), ST_YMax(geom)] as bounds, cartocss_key,name,description,style,group_id,enabled,is_public, source_path from gis.cartocss where cartocss_id=$1', [params.id]).then(el => el.rows?.[0]);
|
|
19
19
|
|
|
20
20
|
if (!cartocss) {
|
|
21
21
|
return reply.status(404).send({ error: `cartocss not found: ${params.id}`, code: 404 });
|
|
@@ -39,6 +39,11 @@ export default async function layerList({
|
|
|
39
39
|
)||'/{z}/{x}/{y}.png',null) as url, 'raster' as service, group_id,
|
|
40
40
|
null as popup, null as card, null as filters, source_path
|
|
41
41
|
from gis.rasters s where is_active and ${!user.uid ? 'is_public' : '1=1'}
|
|
42
|
+
union all
|
|
43
|
+
select cartocss_id as id, name, null as category, style, geom::box2d as bbox, st_asgeojson(geom)::json as geom,
|
|
44
|
+
coalesce('/api/gis-rtile/'||cartocss_id||'/{z}/{x}/{y}.png?nottl=1',null) as url, 'cartocss' as service, group_id,
|
|
45
|
+
null as popup, null as card, null as filters, source_path
|
|
46
|
+
from gis.cartocss where enabled and ${!user.uid ? 'is_public' : '1=1'}
|
|
42
47
|
`;
|
|
43
48
|
|
|
44
49
|
if (user.uid && sql) return q;
|
|
@@ -58,7 +63,7 @@ export default async function layerList({
|
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
// parse service style
|
|
61
|
-
rows.forEach(row => Object.assign(row, { style: row.style ? yml2json(row.style) :
|
|
66
|
+
rows.forEach(row => Object.assign(row, { style: row.style && ['vtile', 'geojson'].includes(row.service) ? yml2json(row.style) : row.style }));
|
|
62
67
|
// parse extent string to number[]
|
|
63
68
|
rows.filter(row => row.extent).forEach(row => Object.assign(row, {
|
|
64
69
|
extent: row.extent.match(/BOX\(([^)]+)\)/)?.[1]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { config, logger, pgClients } from '@opengis/fastify-table/utils.js';
|
|
4
|
+
|
|
5
|
+
import mapnik from '../../../plugins/mapnik/funcs/mapnik.js';
|
|
6
|
+
|
|
7
|
+
const { ClearTile } = mapnik();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Формування растрового tile cartoCss
|
|
11
|
+
*
|
|
12
|
+
* @method GET
|
|
13
|
+
* @alias rtile
|
|
14
|
+
* @param {String} bbox - bbox
|
|
15
|
+
* @param {Number} height - висота по координатам
|
|
16
|
+
* @param {Number} width - ширина по координатам
|
|
17
|
+
* @param {String} data - стилізація
|
|
18
|
+
* @param {String} lang - мова
|
|
19
|
+
* @param {String} z - координата z
|
|
20
|
+
* @param {String} x - координата y
|
|
21
|
+
* @param {String} y - координата x
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export default async function clearTiles({
|
|
25
|
+
pg = pgClients.client, params, query,
|
|
26
|
+
}, reply) {
|
|
27
|
+
if (!ClearTile) {
|
|
28
|
+
return reply.status(400).send({ error: 'mapnik server address needed', code: 400 });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!params.id) {
|
|
32
|
+
return reply.status(400).send({ error: 'not enough params: id', code: 400 });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const decodedPath = Buffer.from(params.id, 'base64url').toString('utf-8');
|
|
36
|
+
|
|
37
|
+
const raster = pg.pk?.['gis.rasters']
|
|
38
|
+
? await pg.query(`select raster_id, source_path from gis.rasters where raster_id=$1::text union all
|
|
39
|
+
select raster_id, source_path from gis.rasters where source_path=$2::text`, [params.id, decodedPath]).then(el => el.rows?.[0] || {})
|
|
40
|
+
: {};
|
|
41
|
+
|
|
42
|
+
const carto = pg.pk?.['gis.cartocss']
|
|
43
|
+
? await pg.query(`select cartocss_id, source_path from gis.cartocss where cartocss_id=$1::text union all
|
|
44
|
+
select cartocss_id, source_path from gis.cartocss where source_path=$2::text`, [params.id, decodedPath]).then(el => el.rows?.[0] || {})
|
|
45
|
+
: {};
|
|
46
|
+
|
|
47
|
+
if (!raster.raster_id && !carto.cartocss_id) {
|
|
48
|
+
Object.assign(raster, { source_path: decodedPath });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!raster.source_path && !carto.cartocss_id) {
|
|
52
|
+
return reply.status(404).send({ error: 'raster / cartocss not found', code: 404 });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (query.debug) {
|
|
56
|
+
const md5 = raster.source_path
|
|
57
|
+
? createHash('md5').update(raster.source_path).digest('hex')
|
|
58
|
+
: undefined;
|
|
59
|
+
const base64 = raster.source_path
|
|
60
|
+
? Buffer.from(raster.source_path).toString('base64url')
|
|
61
|
+
: undefined;
|
|
62
|
+
return {
|
|
63
|
+
id: raster.raster_id || carto.cartocss_id,
|
|
64
|
+
carto: !!carto.cartocss_id,
|
|
65
|
+
raster: !!raster.raster_id,
|
|
66
|
+
md5_path: md5,
|
|
67
|
+
base64_path: base64,
|
|
68
|
+
source_path: raster.source_path || carto.source_path,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const data = await ClearTile({
|
|
74
|
+
path: carto.cartocss_id ? null : raster.source_path, // for rasters only
|
|
75
|
+
name: carto.cartocss_id && carto.source_path ? `vector/${carto.source_path}` : params.id, // required for cartocss
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// if empty directory not found (no tiles rendered yet)
|
|
79
|
+
if (data.result === 'skip') {
|
|
80
|
+
return reply.status(200).send({ message: 'tiles directory not found', code: 200 });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// if cache deleted or empty directory exists = ok
|
|
84
|
+
if (data.result === 'success') {
|
|
85
|
+
return reply.status(200).send({ message: 'ok', code: 200 });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
throw new Error(data.err);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
logger.file('clearTiles/error', { error: err.toString(), stack: err.stack });
|
|
92
|
+
return reply.status(500).send({ error: config.local ? err.toString() : 'clearTiles error', code: 500 });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -47,12 +47,15 @@ export default async function createXml({
|
|
|
47
47
|
|
|
48
48
|
const callback = eventStream(reply);
|
|
49
49
|
|
|
50
|
+
const proj4 = data.srid && pg.pk?.['public.spatial_ref_sys'] ? await pg.query('select proj4text from public.spatial_ref_sys where srid=$1', [data.srid]).then(el => el.rows?.[0]?.proj4text) : undefined;
|
|
51
|
+
|
|
50
52
|
// create at xml/:id with name only, always - by path
|
|
51
53
|
const resp = await CreateXML({
|
|
52
54
|
path: data.source_path,
|
|
53
55
|
name: id, // create additional xml named as primary key at xml directory
|
|
54
56
|
previewZoom: data.raster_zoom, // preview<=>detailed, default = 16 if file size > 1GB
|
|
55
|
-
srid: data.srid, // auto detected if not supplied
|
|
57
|
+
// srid: data.srid, // auto detected if not supplied
|
|
58
|
+
proj4,
|
|
56
59
|
vrt: query.vrt, // force vrt even if relpath leads to single raster file
|
|
57
60
|
debug: query.debug, // stream debug info
|
|
58
61
|
nocache: query.nocache, // recreate if exists
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import mapnik from '../../../plugins/mapnik/funcs/mapnik.js';
|
|
2
|
+
|
|
3
|
+
const { FileSearch } = mapnik();
|
|
4
|
+
|
|
5
|
+
const maxLimit = 100;
|
|
6
|
+
|
|
7
|
+
// example: GET /api/gis-files?dir=tiles/3828667893214610450/13/4884&key=1&size=1675&period=5m
|
|
8
|
+
export default async function fileSearch({
|
|
9
|
+
query,
|
|
10
|
+
}, reply) {
|
|
11
|
+
if (!FileSearch) {
|
|
12
|
+
return reply.status(400).send({ error: 'mapnik server address needed', code: 400 });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
dir, key, period, size, page,
|
|
17
|
+
} = query;
|
|
18
|
+
|
|
19
|
+
const limit = Math.min(query.limit || 16, maxLimit);
|
|
20
|
+
const offset = page && page > 0 ? (page - 1) * limit : 0;
|
|
21
|
+
|
|
22
|
+
if (!key && !period && !size) {
|
|
23
|
+
return reply.status(400).send({ error: 'not enough query params: key / period / size', code: 400 });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const result = await FileSearch({
|
|
27
|
+
path: dir, key, period, size,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
count: result.count,
|
|
32
|
+
data: (result.files || []).slice(offset, offset + limit).map(el => ({ type: 'file', ...el })),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { config } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
|
|
3
|
+
import mapnik from '../../../plugins/mapnik/funcs/mapnik.js';
|
|
4
|
+
|
|
5
|
+
const { FileStat } = mapnik();
|
|
6
|
+
|
|
7
|
+
// example: GET /api/gis-files?path=tiles/3828667893214610450/13/4884/2828.png
|
|
8
|
+
export default async function fileStat({
|
|
9
|
+
query,
|
|
10
|
+
}, reply) {
|
|
11
|
+
if (!config.mapServerAddress) {
|
|
12
|
+
return reply.status(400).send({ error: 'mapnik server not configured', code: 400 });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (config.ready?.mapnik !== true) {
|
|
16
|
+
return reply.status(400).send({ error: 'mapnik server not ready', code: 400 });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { path } = query || {};
|
|
20
|
+
|
|
21
|
+
if (!path) {
|
|
22
|
+
return reply.status(400).send({ error: 'not enough query params: path', code: 400 });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const data = await FileStat({ path });
|
|
26
|
+
return { data };
|
|
27
|
+
}
|
|
@@ -14,7 +14,7 @@ export default async function rtile({
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const raster = pg.pk?.['gis.rasters']
|
|
17
|
-
? await pg.query('select raster_id, source_path, name, description, raster_key, is_active, is_public from gis.rasters where raster_id=$1::text', [params.id])
|
|
17
|
+
? await pg.query('select raster_id, source_path, name, description, raster_key, is_active, is_public, srid from gis.rasters where raster_id=$1::text', [params.id])
|
|
18
18
|
.then(el => el.rows?.[0] || {})
|
|
19
19
|
: {};
|
|
20
20
|
|
|
@@ -27,12 +27,20 @@ export default async function rtile({
|
|
|
27
27
|
Object.assign(raster, { source_path: decodedPath });
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
const proj4 = raster.srid && pg.pk?.['public.spatial_ref_sys'] ? await pg.query('select proj4text from public.spatial_ref_sys where srid=$1', [raster.srid]).then(el => el.rows?.[0]?.proj4text) : undefined;
|
|
31
|
+
|
|
30
32
|
const { data, cache } = await GetRasterInfo({
|
|
31
33
|
path: raster.source_path,
|
|
34
|
+
// srid: raster.srid,
|
|
35
|
+
// proj4,
|
|
32
36
|
ttl: query.nocache ? '0' : '1h',
|
|
33
37
|
});
|
|
34
38
|
|
|
39
|
+
if (query.debug) {
|
|
40
|
+
return { data, cache, raster };
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
return {
|
|
36
|
-
|
|
37
|
-
};
|
|
44
|
+
...data, ...raster, cache, url: `${prefix}/gis-rtile/${Buffer.from(raster.source_path).toString('base64url')}/{z}/{x}/{y}.png`,
|
|
45
|
+
};
|
|
38
46
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import mapnik from '../../../plugins/mapnik/funcs/mapnik.js';
|
|
2
|
+
|
|
3
|
+
const { ReadDir } = mapnik();
|
|
4
|
+
|
|
5
|
+
// example: GET /api/gis-files?dir=tiles/3828667893214610450
|
|
6
|
+
export default async function readDir({
|
|
7
|
+
query,
|
|
8
|
+
}, reply) {
|
|
9
|
+
if (!ReadDir) {
|
|
10
|
+
return reply.status(400).send({ error: 'mapnik server address needed', code: 400 });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { dir } = query;
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
exists = false, count = 0, files = [], directories = [],
|
|
17
|
+
} = await ReadDir({ path: dir });
|
|
18
|
+
return { exists, count, data: files.map(el => ({ type: 'file', ...el })).concat(directories.map(el => ({ type: 'directory', ...el }))) };
|
|
19
|
+
}
|
|
@@ -51,10 +51,12 @@ export default async function rtile({
|
|
|
51
51
|
|
|
52
52
|
// const relpath = raster ? `/map/raster/${raster.source_path}` : null;
|
|
53
53
|
|
|
54
|
-
const
|
|
55
|
-
? await pg.query('select cartocss_id from gis.cartocss where cartocss_id=$1::text', [id])
|
|
56
|
-
.then(el => el.rows?.[0]
|
|
57
|
-
:
|
|
54
|
+
const carto = pg.pk?.['gis.cartocss']
|
|
55
|
+
? await pg.query('select cartocss_id, source_path from gis.cartocss where cartocss_id=$1::text', [id])
|
|
56
|
+
.then(el => el.rows?.[0] || {})
|
|
57
|
+
: {};
|
|
58
|
+
|
|
59
|
+
const cartoExists = !!carto.cartocss_id;
|
|
58
60
|
|
|
59
61
|
if (!raster.raster_id && !cartoExists) {
|
|
60
62
|
const decodedPath = Buffer.from(id, 'base64url').toString('utf-8');
|
|
@@ -68,30 +70,51 @@ export default async function rtile({
|
|
|
68
70
|
const bbox = mercator.bbox(y, x, z, false, '900913');
|
|
69
71
|
|
|
70
72
|
try {
|
|
73
|
+
const ttl = (query.nocache ? '0' : null)
|
|
74
|
+
|| (cartoExists ? '1h' : null);
|
|
75
|
+
|
|
76
|
+
const md5 = raster.source_path
|
|
77
|
+
? createHash('md5').update(raster.source_path).digest('hex')
|
|
78
|
+
: undefined;
|
|
79
|
+
const base64 = raster.source_path
|
|
80
|
+
? Buffer.from(raster.source_path).toString('base64url')
|
|
81
|
+
: undefined;
|
|
82
|
+
|
|
71
83
|
const data = await RenderTile({
|
|
72
84
|
path: cartoExists ? null : raster.source_path, // for rasters only
|
|
73
|
-
name: id, // required for cartocss
|
|
85
|
+
name: cartoExists && carto.source_path ? `vector/${carto.source_path}` : id, // required for cartocss
|
|
74
86
|
width: 256,
|
|
75
87
|
bbox,
|
|
76
|
-
ttl
|
|
88
|
+
ttl,
|
|
77
89
|
debug: query.debug,
|
|
78
90
|
});
|
|
79
91
|
|
|
80
92
|
if (query.debug) {
|
|
81
|
-
return {
|
|
93
|
+
return {
|
|
94
|
+
...data,
|
|
95
|
+
ttl,
|
|
96
|
+
md5_path: md5,
|
|
97
|
+
base64_path: base64,
|
|
98
|
+
raster: !!raster.raster_id,
|
|
99
|
+
carto: cartoExists,
|
|
100
|
+
};
|
|
82
101
|
}
|
|
83
102
|
|
|
84
103
|
if (data.err) {
|
|
85
|
-
logger.file('rtile/error', {
|
|
104
|
+
logger.file('rtile/error', {
|
|
105
|
+
error: data.err, x, y, z, id: cartoExists ? carto.cartocss_id : raster.raster_id, type: cartoExists ? 'css' : 'raster',
|
|
106
|
+
});
|
|
86
107
|
return reply.status(500).send({ error: config.local ? data.err : 'render error', code: 500 });
|
|
87
108
|
}
|
|
88
109
|
|
|
89
110
|
const buffer = Buffer.from(data.base64, 'base64');
|
|
90
111
|
|
|
91
|
-
return reply.headers({ 'Content-Type': 'image/png', 'Cache-Control': query.nocache ? 'no-store, no-cache, must-revalidate' : 'public, max-age=2592000' }).send(buffer);
|
|
112
|
+
return reply.headers({ 'Content-Type': 'image/png', 'Cache-Control': query.nocache || query.nottl ? 'no-store, no-cache, must-revalidate' : 'public, max-age=2592000' }).send(buffer);
|
|
92
113
|
}
|
|
93
114
|
catch (err) {
|
|
94
|
-
logger.file('rtile/error', {
|
|
115
|
+
logger.file('rtile/error', {
|
|
116
|
+
error: err.toString(), stack: err.stack, x, y, z, id: cartoExists ? carto.cartocss_id : raster.raster_id, type: cartoExists ? 'css' : 'raster',
|
|
117
|
+
});
|
|
95
118
|
return reply.status(500).send({ error: config.local ? err.toString() : 'rtile error', code: 500 });
|
|
96
119
|
}
|
|
97
120
|
}
|
|
@@ -2,6 +2,7 @@ import { pgClients } from "@opengis/fastify-table/utils.js";
|
|
|
2
2
|
|
|
3
3
|
export default async function cartoBounds({ id, dataset }, pg = pgClients.client) {
|
|
4
4
|
const sqlBounds = dataset.map(el => ({ tbl: el.table.split('.'), geom: el.gcol || 'geom' })).map(({ tbl, geom }) => `select ST_EstimatedExtent('${tbl[0]}','${tbl[1]}', '${geom}')`);
|
|
5
|
+
// const sqlBounds = dataset.map(el => ({ srid: el.srid || 4326, tbl: el.table.split('.'), geom: el.gcol || 'geom' })).map(({ tbl, geom, srid }) => `select st_transform(st_setsrid(ST_EstimatedExtent('${tbl[0]}','${tbl[1]}', '${geom}'),${srid}),4326) as st_estimatedextent`);
|
|
5
6
|
|
|
6
7
|
const geom = await pg.query(
|
|
7
8
|
`update gis.cartocss set
|
|
@@ -19,14 +19,14 @@ const getGeomColumn = (geoms, customColumn) => {
|
|
|
19
19
|
import cartoBounds from './cartoBounds.js';
|
|
20
20
|
|
|
21
21
|
function cartoData(el, extent, pg) {
|
|
22
|
-
const minzoom = el.
|
|
22
|
+
const minzoom = el.minzoom ? (+el.minzoom || 5) : 5;
|
|
23
23
|
const maxzoom = el.maxzoom ? (+el.maxzoom || 22) : 22;
|
|
24
24
|
const view = `(select ${el.columns || '1'}, ${el.gcol} from ${el.table} where ${el.query || 'true'} ) as data`;
|
|
25
25
|
const layerBound = extent || [-180, -85, 180, 85];
|
|
26
26
|
|
|
27
27
|
return {
|
|
28
|
-
id: el.
|
|
29
|
-
name: el.
|
|
28
|
+
id: el.key,
|
|
29
|
+
name: el.key,
|
|
30
30
|
properties: { minzoom, maxzoom },
|
|
31
31
|
srs: el.proj4text || '+proj=longlat +datum=WGS84 +no_defs',
|
|
32
32
|
Datasource: {
|
|
@@ -44,7 +44,9 @@ function cartoData(el, extent, pg) {
|
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export default async function uploadXML({
|
|
47
|
+
export default async function uploadXML({
|
|
48
|
+
id, dataset, style, source_path: relpath,
|
|
49
|
+
}, pg) {
|
|
48
50
|
if (!id || !dataset?.length || Array.isArray(dataset) === false) {
|
|
49
51
|
return null;
|
|
50
52
|
}
|
|
@@ -67,7 +69,7 @@ export default async function uploadXML({ id, dataset, style }, pg) {
|
|
|
67
69
|
srsList[srid] = await pg.query(`SELECT proj4text from spatial_ref_sys where srid=$1`, [srid])
|
|
68
70
|
.then(e => e.rows[0]?.proj4text);
|
|
69
71
|
}
|
|
70
|
-
Object.assign(el, { gcol, proj4text: srsList[srid] });
|
|
72
|
+
Object.assign(el, { gcol, proj4text: el.proj4text || srsList[srid] });
|
|
71
73
|
}));
|
|
72
74
|
|
|
73
75
|
const bounds = await cartoBounds({ id, dataset }, pg);
|
|
@@ -96,9 +98,11 @@ export default async function uploadXML({ id, dataset, style }, pg) {
|
|
|
96
98
|
throw new Error('xml generation error');
|
|
97
99
|
}
|
|
98
100
|
const { status } = await UploadXML({
|
|
99
|
-
|
|
101
|
+
path: relpath ? `vector/${relpath}` : null,
|
|
102
|
+
name: relpath ? null : id,
|
|
100
103
|
xml: xmlFileText,
|
|
101
104
|
});
|
|
105
|
+
|
|
102
106
|
if (status !== 'success') {
|
|
103
107
|
throw new Error('xml upload error');
|
|
104
108
|
}
|
|
@@ -5,6 +5,10 @@ import rasterInfo from './controllers/rasterInfo.js';
|
|
|
5
5
|
import mapnikStat from './controllers/mapnikStat.js';
|
|
6
6
|
import mapnikLogger from './controllers/mapnikLogger.js';
|
|
7
7
|
import createXmlMulti from './controllers/createXmlMulti.js';
|
|
8
|
+
import readDir from './controllers/readDir.js';
|
|
9
|
+
import fileStat from './controllers/fileStat.js';
|
|
10
|
+
import fileSearch from './controllers/fileSearch.js';
|
|
11
|
+
import clearTiles from './controllers/clearTiles.js';
|
|
8
12
|
|
|
9
13
|
const publicParams = { config: { policy: 'L0' }, package: 'gis' }; // L0 === public
|
|
10
14
|
const adminParams = { config: { policy: 'L1', role: 'admin' }, package: 'gis' };
|
|
@@ -18,4 +22,8 @@ export default async function route(app) {
|
|
|
18
22
|
app.get('/gis-stat/:period?', adminParams, mapnikStat);
|
|
19
23
|
app.get('/gis-logger/*', adminParams, mapnikLogger);
|
|
20
24
|
app.get('/gis-create-xml/:id?', adminParams, createXmlMulti);
|
|
25
|
+
app.get('/gis-files', adminParams, readDir);
|
|
26
|
+
app.get('/gis-file-info', adminParams, fileStat);
|
|
27
|
+
app.get('/gis-file-search', adminParams, fileSearch);
|
|
28
|
+
app.get('/gis-clear-rtile/:id', adminParams, clearTiles);
|
|
21
29
|
}
|