@opengis/gis 0.2.77 → 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.
@@ -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": "badge"
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": "badge"
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.77",
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.125",
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.2",
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,8 +68,8 @@
68
68
  "sass-embedded": "1.86.3",
69
69
  "typescript": "^5.9.3",
70
70
  "vite": "^6.4.1",
71
- "vue": "^3.5.26",
71
+ "vue": "^3.5.28",
72
72
  "vue-router": "4.5.1",
73
73
  "vuedraggable": "^4.1.0"
74
74
  }
75
- }
75
+ }
@@ -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,6 +113,7 @@ 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 {
@@ -145,6 +184,7 @@ message GetRasterInfoRequest {
145
184
  string path = 1;
146
185
  string ttl = 2;
147
186
  int64 srid = 3;
187
+ string proj4 = 4;
148
188
  }
149
189
 
150
190
  message FileItem {
@@ -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) : null }));
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
- ...data, ...raster, cache, url: `${prefix}/gis-rtile/${Buffer.from(raster.source_path).toString('base64url')}/{z}/{x}/{y}.png`,
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
+ }
@@ -82,7 +82,7 @@ export default async function rtile({
82
82
 
83
83
  const data = await RenderTile({
84
84
  path: cartoExists ? null : raster.source_path, // for rasters only
85
- name: cartoExists && carto.source_path ? carto.source_path : id, // required for cartocss
85
+ name: cartoExists && carto.source_path ? `vector/${carto.source_path}` : id, // required for cartocss
86
86
  width: 256,
87
87
  bbox,
88
88
  ttl,
@@ -91,21 +91,30 @@ export default async function rtile({
91
91
 
92
92
  if (query.debug) {
93
93
  return {
94
- ...data, ttl, md5_path: md5, base64_path: base64,
94
+ ...data,
95
+ ttl,
96
+ md5_path: md5,
97
+ base64_path: base64,
98
+ raster: !!raster.raster_id,
99
+ carto: cartoExists,
95
100
  };
96
101
  }
97
102
 
98
103
  if (data.err) {
99
- logger.file('rtile/error', { error: data.err });
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
+ });
100
107
  return reply.status(500).send({ error: config.local ? data.err : 'render error', code: 500 });
101
108
  }
102
109
 
103
110
  const buffer = Buffer.from(data.base64, 'base64');
104
111
 
105
- 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);
106
113
  }
107
114
  catch (err) {
108
- logger.file('rtile/error', { error: err.toString(), stack: err.stack });
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
+ });
109
118
  return reply.status(500).send({ error: config.local ? err.toString() : 'rtile error', code: 500 });
110
119
  }
111
120
  }
@@ -98,7 +98,7 @@ export default async function uploadXML({
98
98
  throw new Error('xml generation error');
99
99
  }
100
100
  const { status } = await UploadXML({
101
- path: relpath,
101
+ path: relpath ? `vector/${relpath}` : null,
102
102
  name: relpath ? null : id,
103
103
  xml: xmlFileText,
104
104
  });
@@ -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
  }