@opengis/gis 0.1.25 → 0.1.27

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.
@@ -25107,7 +25107,8 @@ const Ab = { class: "w-full flex flex-col bg-white border border-stone-200 shado
25107
25107
  default: tr(() => [
25108
25108
  v[1] || (v[1] = Br(" Перейти на карту "))
25109
25109
  ]),
25110
- _: 1
25110
+ _: 1,
25111
+ __: [1]
25111
25112
  })
25112
25113
  ]),
25113
25114
  default: tr(() => [
@@ -29804,7 +29805,8 @@ const Iw = {
29804
29805
  default: tr(() => [
29805
29806
  E[1] || (E[1] = Br(" Відкрити бокову панель "))
29806
29807
  ]),
29807
- _: 1
29808
+ _: 1,
29809
+ __: [1]
29808
29810
  })
29809
29811
  ]),
29810
29812
  _: 1
@@ -29996,7 +29998,8 @@ function oS(h, d, y, b, k, P) {
29996
29998
  default: tr(() => [
29997
29999
  d[0] || (d[0] = re("div", null, "Повернутися до початкових налаштувань", -1))
29998
30000
  ]),
29999
- _: 1
30001
+ _: 1,
30002
+ __: [0]
30000
30003
  })
30001
30004
  ]);
30002
30005
  }
@@ -30168,7 +30171,8 @@ function dS(h, d, y, b, k, P) {
30168
30171
  default: tr(() => [
30169
30172
  d[0] || (d[0] = re("div", null, "Надрукувати", -1))
30170
30173
  ]),
30171
- _: 1
30174
+ _: 1,
30175
+ __: [0]
30172
30176
  });
30173
30177
  }
30174
30178
  const fS = /* @__PURE__ */ rr(pS, [["render", dS]]), mS = {
@@ -30223,7 +30227,8 @@ function gS(h, d, y, b, k, P) {
30223
30227
  default: tr(() => [
30224
30228
  d[1] || (d[1] = re("div", null, "Перемикач виду", -1))
30225
30229
  ]),
30226
- _: 1
30230
+ _: 1,
30231
+ __: [1]
30227
30232
  });
30228
30233
  }
30229
30234
  const yS = /* @__PURE__ */ rr(mS, [["render", gS]]), _S = {}, vS = {
@@ -30443,7 +30448,8 @@ function OS(h, d, y, b, k, P) {
30443
30448
  default: tr(() => [
30444
30449
  d[7] || (d[7] = re("div", null, "Дізнатися геолокацію", -1))
30445
30450
  ]),
30446
- _: 1
30451
+ _: 1,
30452
+ __: [7]
30447
30453
  })
30448
30454
  ]);
30449
30455
  }
@@ -31203,7 +31209,8 @@ function dA(h, d, y, b, k, P) {
31203
31209
  default: tr(() => [
31204
31210
  d[2] || (d[2] = Br(" Приховати панель "))
31205
31211
  ]),
31206
- _: 1
31212
+ _: 1,
31213
+ __: [2]
31207
31214
  })
31208
31215
  ]),
31209
31216
  re("div", cA, [
@@ -31248,7 +31255,8 @@ function dA(h, d, y, b, k, P) {
31248
31255
  default: tr(() => [
31249
31256
  d[6] || (d[6] = re("div", null, "Виміряти довжину", -1))
31250
31257
  ]),
31251
- _: 1
31258
+ _: 1,
31259
+ __: [6]
31252
31260
  })
31253
31261
  ]);
31254
31262
  }
@@ -31549,7 +31557,8 @@ function PA(h, d, y, b, k, P) {
31549
31557
  default: tr(() => [
31550
31558
  d[1] || (d[1] = Br(" Приховати панель "))
31551
31559
  ]),
31552
- _: 1
31560
+ _: 1,
31561
+ __: [1]
31553
31562
  })
31554
31563
  ]),
31555
31564
  re("div", kA, [
@@ -31586,7 +31595,8 @@ function PA(h, d, y, b, k, P) {
31586
31595
  default: tr(() => [
31587
31596
  d[4] || (d[4] = re("div", null, "Виміряти площу", -1))
31588
31597
  ]),
31589
- _: 1
31598
+ _: 1,
31599
+ __: [4]
31590
31600
  })
31591
31601
  ]);
31592
31602
  }
@@ -32323,9 +32333,8 @@ const IA = /* @__PURE__ */ rr(wA, [["render", PA], ["__scopeId", "data-v-024154e
32323
32333
  key: 0,
32324
32334
  map: y.value,
32325
32335
  activeTool: E.value,
32326
- setActiveTool: Yn,
32327
- ref_for: !0
32328
- }, _t === "home" ? { initialView: Fn.value } : {}, { onCardValuesId: oi }), null, 16, ["map", "activeTool"])) : kt("", !0)
32336
+ setActiveTool: Yn
32337
+ }, { ref_for: !0 }, _t === "home" ? { initialView: Fn.value } : {}, { onCardValuesId: oi }), null, 16, ["map", "activeTool"])) : kt("", !0)
32329
32338
  ], 64))), 128))
32330
32339
  ])) : kt("", !0)
32331
32340
  ], 512), [
@@ -1,5 +1,8 @@
1
1
  component: default
2
2
 
3
+ tokens:
4
+ edit: { table: gis.maps, form: gis.maps.form, id: '{{id}}', ignoreCheck: true }
5
+
3
6
  panels:
4
7
  - type: container # закріплений зліва container col, width як простіше
5
8
  col: 5
@@ -1,5 +1,8 @@
1
1
  component: default
2
2
 
3
+ tokens:
4
+ edit: { table: gis.metadata, form: gis.metadata.form, id: '{{id}}', ignoreCheck: true }
5
+
3
6
  panels:
4
7
  - type: container
5
8
  col: 5
@@ -1,5 +1,8 @@
1
1
  component: default
2
2
 
3
+ tokens:
4
+ edit: { table: gis.rasters, form: gis.rasters.form, id: '{{id}}', ignoreCheck: true }
5
+
3
6
  panels:
4
7
  - type: container
5
8
  col: 5
@@ -1,5 +1,8 @@
1
1
  component: default
2
2
 
3
+ tokens:
4
+ edit: { table: gis.registers, form: gis.registers.form, id: '{{id}}', ignoreCheck: true }
5
+
3
6
  panels:
4
7
  - type: container # закріплений зліва container col, width як простіше
5
8
  col: 5
@@ -1,5 +1,8 @@
1
1
  component: default
2
2
 
3
+ tokens:
4
+ edit: { table: gis.services, form: gis.services.form, id: '{{id}}', ignoreCheck: true }
5
+
3
6
  panels:
4
7
  - type: container # закріплений зліва container col, width як простіше
5
8
  col: 4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/gis",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "type": "module",
5
5
  "author": "Softpro",
6
6
  "main": "dist/import-file.js",
@@ -30,8 +30,10 @@
30
30
  "@grpc/grpc-js": "1.13.4",
31
31
  "@grpc/proto-loader": "0.7.15",
32
32
  "@mapbox/sphericalmercator": "1.2.0",
33
- "@opengis/fastify-table": "1.4.1",
34
- "@opengis/v3-core": "^0.3.187",
33
+ "@opengis/fastify-file": "1.1.2",
34
+ "@opengis/fastify-table": "1.4.15",
35
+ "@opengis/fastify-auth": "1.1.0",
36
+ "@opengis/v3-core": "^0.3.196",
35
37
  "@opengis/v3-filter": "0.1.18",
36
38
  "@turf/turf": "7.2.0",
37
39
  "axios": "1.9.0",
@@ -55,4 +57,4 @@
55
57
  "sass-embedded": "1.86.3",
56
58
  "vite": "^6.3.5"
57
59
  }
58
- }
60
+ }
package/plugin.js CHANGED
@@ -5,8 +5,6 @@ import { config } from '@opengis/fastify-table/utils.js';
5
5
  config.prefix = config.prefix || '/api';
6
6
 
7
7
  config.admin = true; // for user-menu
8
- config.auth = config.auth || {};
9
- config.auth.disable = true; // if fastify-auth not registered, then auth is disabled
10
8
 
11
9
  async function plugin(app, opts = config) {
12
10
  // API
@@ -1,12 +1,13 @@
1
- import fs from 'fs';
2
- import path from 'path';
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
3
  import { createServer } from 'vite';
4
4
 
5
+ import { config } from '@opengis/fastify-table/utils.js';
6
+
5
7
  const isProduction = process.env.NODE_ENV === 'production';
6
8
 
7
9
  console.log({ isProduction });
8
10
 
9
-
10
11
  async function plugin(fastify, opts) {
11
12
 
12
13
  const viteServer = !isProduction ? await createServer({
@@ -28,6 +29,10 @@ async function plugin(fastify, opts) {
28
29
 
29
30
  // this is middleware for vite's dev server
30
31
  fastify.addHook('onRequest', async (request, reply) => {
32
+ const { user } = request.session?.passport || {};
33
+ if (!user && config.pg && !config.auth?.disable) {
34
+ return reply.redirect('/login');
35
+ }
31
36
  const next = () => new Promise((resolve) => {
32
37
  viteServer.middlewares(request.raw, reply.raw, () => resolve());
33
38
  });
@@ -57,6 +62,10 @@ async function plugin(fastify, opts) {
57
62
  fastify.get('/public/*', staticFile);
58
63
 
59
64
  fastify.get('*', async (req, reply) => {
65
+ const { user } = req.session?.passport || {};
66
+ if (!user && config.pg && !config.auth?.disable) {
67
+ return reply.redirect('/login');
68
+ }
60
69
  if (!isProduction) return null; // admin vite
61
70
  const stream = fs.createReadStream('admin/dist/index.html');
62
71
  return reply.type('text/html').send(stream);
@@ -5,6 +5,7 @@ import getLayerGeom from './services/get.layer.geom.js';
5
5
  import gisRegistry from './registers/gis.registry.js';
6
6
  import gisRegistryList from './registers/gis.registry.list.js';
7
7
  import mapRegistry from './registers/map.registry.js';
8
+ import gisExport from './registers/gis.export.js';
8
9
 
9
10
  export default async function route(app) {
10
11
  app.put('/insert-columns/:token', insertColumns);
@@ -17,4 +18,6 @@ export default async function route(app) {
17
18
  app.get('/xml/:id', { config: { policy: ['public'] } }, metadataXML);
18
19
 
19
20
  app.get('/get-layer-geom/:id', { config: { policy: ['public'] } }, getLayerGeom);
21
+
22
+ app.get('/gis-export/:type/:slug', { config: { policy: ['public'] } }, gisExport);
20
23
  }
@@ -0,0 +1,10 @@
1
+ const ContentType = {
2
+ xml: 'application/xml',
3
+ shp: 'application/zip',
4
+ csv: 'text/csv',
5
+ geojson: 'application/vnd.geo+json',
6
+ json: 'application/json',
7
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
8
+ };
9
+
10
+ export default ContentType;
@@ -0,0 +1,89 @@
1
+ import { pgClients } from '@opengis/fastify-table/utils.js';
2
+ import { attachClassifiers } from '../../../gis/registers/funcs/classifiers.js';
3
+ const pg = pgClients.client;
4
+
5
+ export default async function getInfo({ finalInfo, format }, reply) {
6
+ if (!finalInfo) return { error: 'No data found' };
7
+
8
+ const table = finalInfo?.table_name;
9
+ const queryCondition = finalInfo?.query || '1=1';
10
+ const columns = finalInfo?.columns || finalInfo?.attributes || '[]';
11
+ const exportable = columns.filter(c => c.is_export);
12
+ const selectNames = exportable.map(c => `"${c.name}"`);
13
+ const selectSQL = `
14
+ SELECT ${selectNames?.length ? selectNames.join(', ') : ''} ${format === 'geojson' || format === 'shp'? `, ST_AsGeoJSON(geom)::json as geom` : ``}
15
+ FROM ${table}
16
+ WHERE ${queryCondition} ${format === 'geojson' || format === 'shp'? `and geom is not null` : ``}
17
+ `;
18
+
19
+ if (format === 'shp') {
20
+ const { rows: geomTypeRows } = await pg.query(`
21
+ SELECT DISTINCT ST_GeometryType(geom) AS geom_type
22
+ FROM ${table}
23
+ WHERE geom IS NOT NULL
24
+ `);
25
+ const geometryTypes = geomTypeRows.map(row => row.geom_type);
26
+
27
+ const sqlList = geometryTypes.map(type => {
28
+ return {
29
+ type,
30
+ query: `
31
+ SELECT ${selectNames.join(', ')}, ST_AsGeoJSON(geom)::json as geom
32
+ FROM ${table}
33
+ WHERE ${queryCondition} AND ST_GeometryType(geom) = '${type}' AND geom IS NOT NULL
34
+ `,
35
+ };
36
+ });
37
+
38
+ const { rows: [{ srid }] } = await pg.query(`
39
+ SELECT ST_SRID(geom) AS srid
40
+ FROM ${table}
41
+ WHERE geom IS NOT NULL
42
+ LIMIT 1;
43
+ `);
44
+
45
+ return {
46
+ columns: exportable,
47
+ table,
48
+ srid: srid || 4326,
49
+ geomTypes: geometryTypes,
50
+ sqlList
51
+ };
52
+ }
53
+
54
+ const { rows } = await pg.query(selectSQL);
55
+ if (!rows?.length) {
56
+ return reply.status(404).send({ message: 'Немає даних, які можна експортувати', status: 404 });
57
+ }
58
+
59
+ const classifiers = exportable
60
+ .filter(col => col.data && ["select", "badge", "tags"].includes(col.format))
61
+ .map(col => ({ name: col.name, classifier: col.data }));
62
+
63
+ const rowsWithClassifiers = await attachClassifiers(rows, classifiers);
64
+
65
+ const simplifiedRows = rowsWithClassifiers.map(row => {
66
+ const newRow = { ...row };
67
+
68
+ for (const key of Object.keys(newRow)) {
69
+ if (key.endsWith('_data') && typeof newRow[key]?.text === 'string') {
70
+ const baseKey = key.replace(/_data$/, '');
71
+ newRow[baseKey] = newRow[key].text;
72
+ }
73
+
74
+ if (key.endsWith('_text') && typeof newRow[key] === 'string') {
75
+ const baseKey = key.replace(/_text$/, '');
76
+ newRow[baseKey] = newRow[key];
77
+ }
78
+ }
79
+
80
+ return newRow;
81
+ });
82
+
83
+ return {
84
+ rows: simplifiedRows,
85
+ data: rows,
86
+ columns: exportable,
87
+ table
88
+ };
89
+ }
@@ -0,0 +1,149 @@
1
+ import { createReadStream } from 'fs';
2
+ import { mkdir, writeFile } from 'node:fs/promises';
3
+ import path from 'path';
4
+ import { config, getMeta, getFolder } from '@opengis/fastify-table/utils.js';
5
+ import { grpc } from '@opengis/fastify-file/utils.js';
6
+ import convertJSONToCSV from '@opengis/fastify-file/server/routes/file/controllers/utils/convertJSONToCSV.js';
7
+ import getInfo from './funcs/get.info.js';
8
+ import ContentType from './funcs/content.type.js';
9
+ const { jsonToXls, geojsonToShp } = grpc();
10
+ const rootDir = getFolder(config, 'local');
11
+
12
+ /**
13
+ * Експорт даних реєстру
14
+ * @param {Object} query - Об'єкт запиту з форматом.
15
+ * @param {Object} params - Об'єкт параметрів з slug та type.
16
+ * @param {Function} reply
17
+ * @returns {boolean} File
18
+ */
19
+
20
+ export default async function gisExport({ pg, query = {}, params = {} }, reply) {
21
+ const { format } = query;
22
+ const { slug, type } = params;
23
+ if (!['csv', 'xlsx', 'shp', 'geojson'].includes(format)) {
24
+ return reply.code(401).send({ message: 'Param format is invalid. Allowed: csv, xlsx, shp, geojson', status: 404 });
25
+ }
26
+ if (!slug) return reply.code(401).send({ message: 'slug is not defined', status: 404 });
27
+ if (!type) return reply.code(401).send({ message: 'type is not defined', status: 404 });
28
+
29
+ let registerInfo;
30
+ let serviceInfo;
31
+
32
+ if (type === 'register') {
33
+ registerInfo = await pg.query(
34
+ `SELECT table_name, columns, query
35
+ FROM gis.registers
36
+ WHERE register_key = $1`,
37
+ [slug]
38
+ );
39
+ }
40
+ if (type === 'service') {
41
+ serviceInfo = await pg.query(
42
+ `SELECT source_path as table_name, attributes as columns, query
43
+ FROM gis.services
44
+ WHERE service_key = $1`,
45
+ [slug]
46
+ );
47
+ }
48
+ const finalInfo = registerInfo?.rows[0] || serviceInfo?.rows[0];
49
+
50
+ const exportable = finalInfo?.columns?.filter(c => c.is_export);
51
+ const colmodel = exportable.map(col => ({
52
+ name: col.name,
53
+ title: col.ua || col.name
54
+ }));
55
+ const { rows, table, srid, sqlList } = await getInfo({ finalInfo, format }, reply);
56
+ let filePath = ['csv', 'geojson', 'xlsx'].includes(format) ? path.join(rootDir, `/files/tmp/export_${table}_${Date.now()}.${format}`) : path.join(rootDir, `/files/tmp/export_${table}_${Date.now()}.zip`);
57
+ const ext = path.extname(filePath);
58
+ const filePathJSON = ['csv', 'geojson', 'shp'].includes(format) ? filePath.replace(ext, '.json') : filePath;
59
+ let exportedZipPaths = [];
60
+
61
+ const meta = await getMeta({ pg, table: table });
62
+ if ((format === 'geojson' || format === 'shp') && !meta?.geom) {
63
+ return reply.status(400).send('Ця таблиця не містить полів геометрії. Виберіть тип, який не потребує геометрії для вивантаження');
64
+ }
65
+
66
+ if (format === 'xlsx') {
67
+ if (!jsonToXls) {
68
+ const message = 'Сервіс конвертації jsonToXls тимчасово недоступний. Спробуйте експортувати дані у іншому форматі...';
69
+ const error = new Error(message);
70
+ error.status = 503;
71
+
72
+ throw error;
73
+ }
74
+ const { result } = await jsonToXls({
75
+ json: JSON.stringify(rows),
76
+ colmodel: JSON.stringify(colmodel),
77
+ });
78
+
79
+ await mkdir(path.dirname(filePath), { recursive: true });
80
+ await writeFile(filePath, result, 'base64');
81
+ }
82
+
83
+ if (format === 'csv') {
84
+ await mkdir(path.dirname(filePathJSON), { recursive: true });
85
+ await writeFile(filePathJSON, JSON.stringify(rows));
86
+
87
+ await convertJSONToCSV({
88
+ filePath: filePathJSON, colmodel, columnList: colmodel.map(c => c.name),
89
+ });
90
+ }
91
+
92
+ if (['shp', 'geojson'].includes(format)) {
93
+ if (format === 'geojson') {
94
+ const geojson = {
95
+ type: 'FeatureCollection',
96
+ features: rows.map((row) => ({
97
+ type: 'Feature',
98
+ geometry: row.geom,
99
+ properties: Object.fromEntries(Object.entries(row).filter(([key]) => key !== 'geom')),
100
+ })),
101
+ };
102
+ filePath = filePathJSON.replace('.json', '.geojson');
103
+ await mkdir(path.dirname(filePath), { recursive: true });
104
+ await writeFile(filePath, JSON.stringify(geojson));
105
+ }
106
+
107
+ if (format === 'shp') {
108
+ const proj = `EPSG:${srid || 4326}`;
109
+ for (const { type, query } of sqlList) {
110
+ const { rows } = await pg.query(query);
111
+ if (!rows.length) continue;
112
+
113
+ const geojson = {
114
+ type: 'FeatureCollection',
115
+ features: rows.map(row => ({
116
+ type: 'Feature',
117
+ geometry: row.geom,
118
+ properties: Object.fromEntries(Object.entries(row).filter(([k]) => k !== 'geom')),
119
+ })),
120
+ };
121
+ const geomShort = type.toLowerCase().replace('st_', '');
122
+ const typedPath = filePath.replace('.zip', `_${geomShort}.zip`);
123
+
124
+ const filePathTyped = filePath.replace('.zip', `_${type.toLowerCase().replace('st_', '')}.zip`);
125
+ const { result: shpZipBuffer } = await geojsonToShp({
126
+ geojson: JSON.stringify(geojson),
127
+ proj,
128
+ });
129
+
130
+ await mkdir(path.dirname(filePathTyped), { recursive: true });
131
+ await writeFile(filePathTyped, Buffer.from(shpZipBuffer, 'base64'));
132
+
133
+ exportedZipPaths.push(typedPath);
134
+ }
135
+ }
136
+ }
137
+
138
+ let exportFilePath = filePath;
139
+
140
+ if (format === 'shp' && exportedZipPaths?.length > 0) {
141
+ exportFilePath = exportedZipPaths[0];
142
+ }
143
+
144
+ reply.header('Content-Disposition', `attachment; filename=${encodeURIComponent(path.basename(exportFilePath))}`);
145
+ reply.header('Content-Type', ContentType[format]);
146
+
147
+ const fileStream = createReadStream(exportFilePath);
148
+ return reply.send(fileStream);
149
+ }