@opengis/gis 0.2.154 → 0.2.156

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.
Files changed (26) hide show
  1. package/dist/{CardIcon--lAtW-9w.js → CardIcon-D-1qypTS.js} +1 -1
  2. package/dist/{EntityTablePage-C4xpTFhX.js → EntityTablePage-DP9Ob7Iw.js} +1 -1
  3. package/dist/{ExtentOutlineLayer.vue_vue_type_script_setup_true_lang-ChIqnIJk.js → ExtentOutlineLayer.vue_vue_type_script_setup_true_lang-CtT-2WgB.js} +1 -1
  4. package/dist/{HeaderActions.vue_vue_type_script_setup_true_lang-ZusIbDWr.js → HeaderActions.vue_vue_type_script_setup_true_lang-DMkvpe25.js} +1 -1
  5. package/dist/{MapSettings-DW8jEvOa.js → MapSettings-DrCqSvlK.js} +1 -1
  6. package/dist/{RastersTablePage-BiLlnxUp.js → RastersTablePage-DlITcUym.js} +1 -1
  7. package/dist/{cartocss-CLomvOS2.js → cartocss-D-DI_wOE.js} +5 -5
  8. package/dist/{import-utils-kb-uej6A.js → import-utils-CC4-riao.js} +1 -1
  9. package/dist/index-DUvc4hNZ.js +11508 -0
  10. package/dist/index.css +1 -1
  11. package/dist/index.js +1 -1
  12. package/dist/index.umd.cjs +32 -32
  13. package/dist/{raster-DwJbTo-Q.js → raster-DlWMWAbc.js} +3 -3
  14. package/dist/{register-O9oHRhKY.js → register-BVrXDoQg.js} +3 -3
  15. package/dist/{service-Bej37Mtm.js → service-DlN4jqsS.js} +5 -5
  16. package/dist/{vs-datatable-ChBQO9dx.js → vs-datatable-B9s6O_XG.js} +1 -1
  17. package/module/gis/select/srid.sql +7 -0
  18. package/module/test/map/main.json +2 -1
  19. package/package.json +74 -74
  20. package/server/plugins/mapnik/map.proto +41 -1
  21. package/server/routes/gis/services/get.service.users.js +74 -74
  22. package/server/routes/map/controllers/gis.convert.js +53 -0
  23. package/server/routes/map/controllers/parseGeoFile.js +86 -0
  24. package/server/routes/map/index.mjs +33 -4
  25. package/server/routes/mapnik/controllers/rtile.js +3 -2
  26. package/dist/index-BlZ7asai.js +0 -10804
@@ -1,9 +1,9 @@
1
1
  import { defineComponent as te, resolveDirective as We, openBlock as u, createElementBlock as m, createElementVNode as n, Fragment as W, renderList as X, toDisplayString as d, withDirectives as we, createTextVNode as Xe, normalizeClass as ae, ref as $, watch as S, computed as h, vModelText as Ke, onMounted as Ye, onBeforeUnmount as Ze, createCommentVNode as q, createVNode as B, withCtx as He, createBlock as F, resolveDynamicComponent as Je } from "vue";
2
2
  import { useRouter as et, useRoute as tt } from "vue-router";
3
3
  import { TooltipDirective as st, notify as M } from "@opengis/core";
4
- import { _ as nt, u as lt, f as rt, M as ot, i as it, j as be, F as at } from "./index-BlZ7asai.js";
5
- import { _ as ut, a as ct, b as dt, c as ft } from "./HeaderActions.vue_vue_type_script_setup_true_lang-ZusIbDWr.js";
6
- import { _ as he } from "./ExtentOutlineLayer.vue_vue_type_script_setup_true_lang-ChIqnIJk.js";
4
+ import { _ as nt, u as lt, f as rt, M as ot, i as it, j as be, F as at } from "./index-DUvc4hNZ.js";
5
+ import { _ as ut, a as ct, b as dt, c as ft } from "./HeaderActions.vue_vue_type_script_setup_true_lang-DMkvpe25.js";
6
+ import { _ as he } from "./ExtentOutlineLayer.vue_vue_type_script_setup_true_lang-CtT-2WgB.js";
7
7
  const mt = { class: "flex-1 overflow-y-auto p-4 sm:p-6" }, pt = { class: "space-y-3" }, gt = { class: "bg-white p-3 rounded-xl border border-gray-200 shadow-sm" }, yt = { class: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2" }, vt = { class: "text-xs text-gray-500 mb-1" }, xt = {
8
8
  key: 0,
9
9
  class: "text-base text-md font-semibold text-gray-900"
@@ -2,9 +2,9 @@ import { defineComponent as B, mergeModels as j, useModel as S, computed as h, d
2
2
  import { useRoute as Z, useRouter as ee } from "vue-router";
3
3
  import { notify as F } from "@opengis/core";
4
4
  import { _ as te } from "./TableFormHeader.vue_vue_type_script_setup_true_lang-Mci_tseM.js";
5
- import { V as N } from "./vs-datatable-ChBQO9dx.js";
6
- import { _ as z, n as R, m as E, r as H } from "./import-utils-kb-uej6A.js";
7
- import { a as ae, b as le, c as se, d as oe, e as ie } from "./index-BlZ7asai.js";
5
+ import { V as N } from "./vs-datatable-B9s6O_XG.js";
6
+ import { _ as z, n as R, m as E, r as H } from "./import-utils-CC4-riao.js";
7
+ import { a as ae, b as le, c as se, d as oe, e as ie } from "./index-DUvc4hNZ.js";
8
8
  import { LucideChevronRight as re } from "lucide-vue-next";
9
9
  import { _ as ne } from "./MapSettingsTabs.vue_vue_type_script_setup_true_lang-CKCK3NqY.js";
10
10
  const ue = { class: "vs-form" }, de = { class: "vs-form__body auto-rows-max container" }, ce = { class: "flex flex-wrap" }, me = { class: "h-fit w-full mb-6 bg-white border border-gray-200 rounded-lg p-6" }, ve = { class: "h-fit w-full flex flex-wrap" }, pe = { class: "w-full" }, fe = /* @__PURE__ */ B({
@@ -1,12 +1,12 @@
1
1
  import { openBlock as C, createElementBlock as D, createElementVNode as v, defineComponent as ee, ref as S, watch as I, createTextVNode as Gi, createVNode as j, createBlock as M, createCommentVNode as q, defineAsyncComponent as Qi, unref as Te, withCtx as We, withDirectives as qe, vModelText as Ke, normalizeClass as ve, Fragment as Hn, renderList as Vn, toDisplayString as le, getCurrentInstance as qn, onBeforeUnmount as Kn, toRef as Wi, computed as ue, onMounted as Ji, resolveDynamicComponent as Zi } from "vue";
2
2
  import { useRoute as Xi, useRouter as el } from "vue-router";
3
3
  import { notify as Q } from "@opengis/core";
4
- import { _ as Re, u as Ie, f as nl, M as il, g as ll, h as rl, i as tl, j as kn, k as ol, l as ul, m as al, s as Cn, n as sl } from "./index-BlZ7asai.js";
5
- import { _ as cl, a as fl, b as dl, c as pl } from "./HeaderActions.vue_vue_type_script_setup_true_lang-ZusIbDWr.js";
6
- import { S as ml, C as hl } from "./CardIcon--lAtW-9w.js";
4
+ import { _ as Re, u as Ie, f as nl, M as il, g as ll, h as rl, i as tl, j as kn, k as ol, l as ul, m as al, s as Cn, n as sl } from "./index-DUvc4hNZ.js";
5
+ import { _ as cl, a as fl, b as dl, c as pl } from "./HeaderActions.vue_vue_type_script_setup_true_lang-DMkvpe25.js";
6
+ import { S as ml, C as hl } from "./CardIcon-D-1qypTS.js";
7
7
  import { _ as zn } from "./MonacoEditor.vue_vue_type_script_setup_true_lang-D3ZPeXMw.js";
8
- import { V as $e } from "./vs-datatable-ChBQO9dx.js";
9
- import { _ as ln, m as rn, r as xe } from "./import-utils-kb-uej6A.js";
8
+ import { V as $e } from "./vs-datatable-B9s6O_XG.js";
9
+ import { _ as ln, m as rn, r as xe } from "./import-utils-CC4-riao.js";
10
10
  /*! js-yaml 4.1.1 https://github.com/nodeca/js-yaml @license MIT */
11
11
  function Gn(e) {
12
12
  return typeof e > "u" || e === null;
@@ -1,7 +1,7 @@
1
1
  import { defineComponent as F, mergeModels as O, defineAsyncComponent as I, useModel as R, ref as y, watch as K, openBlock as o, createElementBlock as s, createElementVNode as e, createVNode as $, unref as x, withCtx as S, createStaticVNode as V, useSlots as P, computed as H, Fragment as b, createTextVNode as U, createCommentVNode as N, renderList as A, normalizeStyle as Q, toDisplayString as M, normalizeClass as z, withModifiers as X, createBlock as Y, resolveDynamicComponent as ee, withDirectives as te, renderSlot as ne } from "vue";
2
2
  import { notify as E } from "@opengis/core";
3
3
  import { Edit as le, Trash as oe } from "lucide-vue-next";
4
- import { _ as u } from "./index-BlZ7asai.js";
4
+ import { _ as u } from "./index-DUvc4hNZ.js";
5
5
  const se = { class: "flex items-center justify-center col-span-3 gap-2" }, re = { class: "flex justify-end p-[20px] gap-[10px] border-t w-full" }, ie = /* @__PURE__ */ F({
6
6
  __name: "vs-datatable-controls",
7
7
  props: /* @__PURE__ */ O({
@@ -0,0 +1,7 @@
1
+ select
2
+ srid::text as id,
3
+ auth_name || ' - ' || auth_srid as text
4
+ from
5
+ public.spatial_ref_sys
6
+ order by
7
+ srid asc
@@ -33,6 +33,7 @@
33
33
  "length",
34
34
  "ruler",
35
35
  "area",
36
- "print"
36
+ "print",
37
+ "add-data"
37
38
  ]
38
39
  }
package/package.json CHANGED
@@ -1,75 +1,75 @@
1
- {
2
- "name": "@opengis/gis",
3
- "version": "0.2.154",
4
- "type": "module",
5
- "author": "Softpro",
6
- "main": "./dist/index.js",
7
- "files": [
8
- "dist/*",
9
- "module/*",
10
- "server/*",
11
- "plugin.js",
12
- "utils.js",
13
- "config.js",
14
- "README.md",
15
- "LICENSE"
16
- ],
17
- "scripts": {
18
- "dump": "bun ./node_modules/@opengis/fastify-table/dist/script/dump.js",
19
- "migrate": "MIGRATE=true bun ./node_modules/@opengis/fastify-table/dist/script/migrate.js",
20
- "start": "bun server",
21
- "start:kyiv-demo": "bun --env-file=.env.kyiv-demo server",
22
- "kamyanske": "bun --env-file=.env.kamyanske server",
23
- "start:kr": "bun --env-file=.env.kr server",
24
- "poltava": "bun --env-file=.env.poltava --hot server",
25
- "dev:kyiv-demo": "bun --env-file=.env.kyiv-demo --hot server",
26
- "dev:kamyanske": "bun --env-file=.env.kamyanske --hot server",
27
- "kr": "bun --env-file=.env.kr --hot server",
28
- "lint": "eslint src --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts",
29
- "fix": "eslint src --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
30
- "patch": "npm version patch && git push && npm publish",
31
- "dev": "bun --hot server",
32
- "front": "vite dev",
33
- "build-lib": "vite build",
34
- "build:admin": "vite build admin",
35
- "build": "vite build",
36
- "preview": "vite preview",
37
- "prod": "NODE_ENV=production node server",
38
- "docs:install": "npm install --prefix ./docs",
39
- "docs:dev": "npm run --prefix ./docs docs:dev",
40
- "docs:build": "npm run --prefix ./docs docs:build",
41
- "docs:preview": "npm run --prefix ./docs docs:preview",
42
- "prepublishOnly": "npm run build"
43
- },
44
- "dependencies": {
45
- "@mapbox/sphericalmercator": "1.2.0",
46
- "carto": "0.16.3"
47
- },
48
- "peerDependencies": {
49
- "@opengis/fastify-table": "^2.3.4"
50
- },
51
- "resolutions": {
52
- "rollup": "4.30.0"
53
- },
54
- "devDependencies": {
55
- "@opengis/core": "^0.0.23",
56
- "@opengis/fastify-table": "^2.4.10",
57
- "@opengis/filter": "0.1.35",
58
- "@opengis/form": "^0.0.133",
59
- "@opengis/table": "^0.0.27",
60
- "@vitejs/plugin-vue": "^5.2.4",
61
- "eslint": "^8.57.1",
62
- "eslint-config-airbnb": "19.0.4",
63
- "eslint-plugin-import": "^2.32.0",
64
- "eslint-plugin-vue": "^9.33.0",
65
- "@vue/eslint-config-typescript": "^12.0.0",
66
- "vue-eslint-parser": "^10.4.1",
67
- "lucide-vue-next": "^0.577.0",
68
- "sass-embedded": "1.86.3",
69
- "typescript": "^5.9.3",
70
- "vite": "^6.4.3",
71
- "vue": "^3.5.38",
72
- "vue-router": "4.5.1",
73
- "vuedraggable": "^4.1.0"
74
- }
1
+ {
2
+ "name": "@opengis/gis",
3
+ "version": "0.2.156",
4
+ "type": "module",
5
+ "author": "Softpro",
6
+ "main": "./dist/index.js",
7
+ "files": [
8
+ "dist/*",
9
+ "module/*",
10
+ "server/*",
11
+ "plugin.js",
12
+ "utils.js",
13
+ "config.js",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "dump": "bun ./node_modules/@opengis/fastify-table/dist/script/dump.js",
19
+ "migrate": "MIGRATE=true bun ./node_modules/@opengis/fastify-table/dist/script/migrate.js",
20
+ "start": "bun server",
21
+ "start:kyiv-demo": "bun --env-file=.env.kyiv-demo server",
22
+ "kamyanske": "bun --env-file=.env.kamyanske server",
23
+ "start:kr": "bun --env-file=.env.kr server",
24
+ "poltava": "bun --env-file=.env.poltava --hot server",
25
+ "dev:kyiv-demo": "bun --env-file=.env.kyiv-demo --hot server",
26
+ "dev:kamyanske": "bun --env-file=.env.kamyanske --hot server",
27
+ "kr": "bun --env-file=.env.kr --hot server",
28
+ "lint": "eslint src --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts",
29
+ "fix": "eslint src --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
30
+ "patch": "npm version patch && git push && npm publish",
31
+ "dev": "bun --hot server",
32
+ "front": "vite dev",
33
+ "build-lib": "vite build",
34
+ "build:admin": "vite build admin",
35
+ "build": "vite build",
36
+ "preview": "vite preview",
37
+ "prod": "NODE_ENV=production node server",
38
+ "docs:install": "npm install --prefix ./docs",
39
+ "docs:dev": "npm run --prefix ./docs docs:dev",
40
+ "docs:build": "npm run --prefix ./docs docs:build",
41
+ "docs:preview": "npm run --prefix ./docs docs:preview",
42
+ "prepublishOnly": "npm run build"
43
+ },
44
+ "dependencies": {
45
+ "@mapbox/sphericalmercator": "1.2.0",
46
+ "carto": "0.16.3"
47
+ },
48
+ "peerDependencies": {
49
+ "@opengis/fastify-table": "^2.3.4"
50
+ },
51
+ "resolutions": {
52
+ "rollup": "4.30.0"
53
+ },
54
+ "devDependencies": {
55
+ "@opengis/core": "^0.0.23",
56
+ "@opengis/fastify-table": "^2.4.10",
57
+ "@opengis/filter": "0.1.35",
58
+ "@opengis/form": "^0.0.133",
59
+ "@opengis/table": "^0.0.27",
60
+ "@vitejs/plugin-vue": "^5.2.4",
61
+ "eslint": "^8.57.1",
62
+ "eslint-config-airbnb": "19.0.4",
63
+ "eslint-plugin-import": "^2.32.0",
64
+ "eslint-plugin-vue": "^9.33.0",
65
+ "@vue/eslint-config-typescript": "^12.0.0",
66
+ "vue-eslint-parser": "^10.4.1",
67
+ "lucide-vue-next": "^0.577.0",
68
+ "sass-embedded": "1.86.3",
69
+ "typescript": "^5.9.3",
70
+ "vite": "^6.4.3",
71
+ "vue": "^3.5.38",
72
+ "vue-router": "4.5.1",
73
+ "vuedraggable": "^4.1.0"
74
+ }
75
75
  }
@@ -8,6 +8,7 @@ package map;
8
8
  // Map Service
9
9
  // ----------------------------
10
10
  service MapService {
11
+ rpc PrintMap(PrintMapRequest) returns (PrintMapOut);
11
12
  rpc RenderTile(RenderTileRequest) returns (RenderTileOut);
12
13
  rpc ClearTile(ClearTileRequest) returns (ClearTileOut);
13
14
  rpc ReadDir(ReadDirRequest) returns (ReadDirOut);
@@ -17,6 +18,7 @@ service MapService {
17
18
  rpc CreateIndex(CreateIndexRequest) returns (CreateIndexOut); // create/update index.geojson for folder
18
19
  rpc ConvertLog(ConvertLogRequest) returns (ConvertLogOut); // json state for current stream tasks
19
20
  rpc ConvertCog(ConvertCogRequest) returns (stream JobProgress); // streaming COG conversion
21
+ rpc ConvertVector(ConvertVectorRequest) returns (ConvertVectorOut); // vector file/archive -> GeoJSON
20
22
  rpc ImportData(ImportDataRequest) returns (stream JobProgress); // streaming SHP -> PostGIS import
21
23
  rpc UploadXML(UploadXMLRequest) returns (UploadXMLOut);
22
24
  rpc UploadRaster(UploadRasterRequest) returns (UploadRasterOut);
@@ -30,6 +32,26 @@ service MapService {
30
32
  // ----------------------------
31
33
  // Messages for MapService
32
34
  // ----------------------------
35
+ message PrintMapRequest {
36
+ string path = 1;
37
+ string geojson = 2;
38
+ repeated double bbox = 3; // [minx, miny, maxx, maxy]
39
+ int32 width = 4;
40
+ int32 height = 5;
41
+ string ttl = 6; // "5m", "1h", "1d" or "0"
42
+ optional bool base64 = 7; // optional output mode flag: true -> base64, false -> raw bytes
43
+ int32 opacity = 8; // 0-255
44
+ repeated string colors = 9; // [ #00FF00, #0000FF]
45
+ repeated string layers = 10;
46
+ }
47
+
48
+ message PrintMapOut {
49
+ string base64 = 1;
50
+ bool cache = 2;
51
+ string err = 3;
52
+ bytes data = 7; // raw PNG bytes (used when request.base64=false)
53
+ }
54
+
33
55
  message RenderTileRequest {
34
56
  string name = 1;
35
57
  string path = 2;
@@ -42,6 +64,7 @@ message RenderTileRequest {
42
64
  optional int32 y = 9; // optional XYZ tile y
43
65
  bool meta = 10; // when true return tile metadata/path only (without base64 image payload)
44
66
  optional bool base64 = 11; // optional output mode flag: true -> base64, false -> raw bytes
67
+ optional string format = 12; // optional tile image format: png|jpg|jpeg|webp
45
68
  }
46
69
 
47
70
  message RenderTileOut {
@@ -182,10 +205,27 @@ message ConvertCogRequest {
182
205
  int32 srid = 7; // optional target SRID for reprojection before COG conversion
183
206
  }
184
207
 
208
+ message ConvertVectorRequest {
209
+ bytes data = 1; // input vector file/archive bytes
210
+ string format = 2; // optional input format hint: zip|shp|kml|gml|geojson|json
211
+ int32 srs = 3; // target EPSG code, default 4326
212
+ string filename = 4; // optional original filename for extension/layer detection
213
+ }
214
+
215
+ message ConvertVectorOut {
216
+ string geojson = 1; // converted GeoJSON payload
217
+ string err = 2; // error text when failed
218
+ int64 size = 3; // output GeoJSON byte size
219
+ }
220
+
185
221
  message ImportDataRequest {
186
222
  string path = 1; // path to .shp file or directory under vector root
187
223
  bool recursive = 2; // process nested folders when path is directory
188
224
  bool force = 3; // overwrite existing tables when true
225
+ string mode = 4; // import mode: file|type (aliases: 1|2)
226
+ string db = 5; // PostgreSQL URL: postgresql://user:pass@host:port/dbname
227
+ string schema = 6; // target schema override
228
+ string type = 7; // optional geometry type override, e.g. LineString
189
229
  }
190
230
 
191
231
  message UploadXMLRequest {
@@ -229,7 +269,7 @@ message GetStatRequest {
229
269
  }
230
270
 
231
271
  message GetStatOut {
232
- google.protobuf.Struct result = 1; // dynamic json structure, not bound by same value type like with map
272
+ google.protobuf.Struct result = 1; // dynamic stats structure
233
273
  }
234
274
 
235
275
  message GetLogsRequest {
@@ -1,74 +1,74 @@
1
- import { pgClients } from '@opengis/fastify-table/utils.js';
2
-
3
- /**
4
- * Апі повертає перелік користувачів, які мають доступ до георесурсу
5
- *
6
- * @method GET
7
- * @summary Апі повертає перелік користувачів, які мають доступ до георесурсу
8
- * @alias getServiceUsers
9
- * @tag services
10
- * @param {String} params.id Ідентифікатор георесурсу (service_id)
11
- * @param {Number} query.limit Кількість записів на сторінці (макс. 100, за замовчуванням 20)
12
- * @param {Number} query.offset Зміщення для пагінації
13
- * @param {String} query.search Текстовий пошук за прізвищем, іменем, по-батькові або логіном
14
- * @type api
15
- *
16
- * @example /api/gis-service-users/3054579335420708639?limit=8&offset=0
17
- */
18
- export default async function getServiceUsers({ params = {}, query = {}, pg = pgClients.client }, reply) {
19
- const { id } = params;
20
- const limit = Math.min(parseInt(query.limit, 10) || 20, 100);
21
- const offset = parseInt(query.offset, 10) || 0;
22
- const search = query.search?.trim() || '';
23
-
24
- if (!id) return reply.status(400).send({ message: 'service_id is required' });
25
-
26
- const searchFilter = search
27
- ? `and (
28
- coalesce(b.sur_name,'') || ' ' || coalesce(b.user_name,'') || ' ' || coalesce(b.father_name,'') ilike $2
29
- or b.login ilike $2
30
- or b.uid::text ilike $2
31
- )`
32
- : '';
33
-
34
- const sql = `
35
- select
36
- a.user_id,
37
- a.actions,
38
- coalesce(
39
- nullif(
40
- coalesce(b.sur_name,'') || coalesce(' ' || b.user_name,'') || coalesce(' ' || b.father_name,''),
41
- ''
42
- ),
43
- b.login,
44
- b.uid::text,
45
- a.user_id
46
- ) as user_full_name
47
- from gis.services_users_rel a
48
- left join admin.users b on b.uid = a.user_id
49
- where a.service_id = $1
50
- ${searchFilter}
51
- order by user_full_name
52
- limit ${limit} offset ${offset}
53
- `;
54
-
55
- const countSql = `
56
- select count(*)::int as total
57
- from gis.services_users_rel a
58
- left join admin.users b on b.uid = a.user_id
59
- where a.service_id = $1
60
- ${searchFilter}
61
- `;
62
-
63
- const args = search ? [id, `%${search}%`] : [id];
64
-
65
- const [{ rows }, countResult] = await Promise.all([
66
- pg.query(sql, args),
67
- pg.query(countSql, args).then((r) => r.rows?.[0]?.total ?? 0),
68
- ]);
69
-
70
- return reply.status(200).send({
71
- rows,
72
- count: countResult,
73
- });
74
- }
1
+ import { pgClients } from '@opengis/fastify-table/utils.js';
2
+
3
+ /**
4
+ * Апі повертає перелік користувачів, які мають доступ до георесурсу
5
+ *
6
+ * @method GET
7
+ * @summary Апі повертає перелік користувачів, які мають доступ до георесурсу
8
+ * @alias getServiceUsers
9
+ * @tag services
10
+ * @param {String} params.id Ідентифікатор георесурсу (service_id)
11
+ * @param {Number} query.limit Кількість записів на сторінці (макс. 100, за замовчуванням 20)
12
+ * @param {Number} query.offset Зміщення для пагінації
13
+ * @param {String} query.search Текстовий пошук за прізвищем, іменем, по-батькові або логіном
14
+ * @type api
15
+ *
16
+ * @example /api/gis-service-users/3054579335420708639?limit=8&offset=0
17
+ */
18
+ export default async function getServiceUsers({ params = {}, query = {}, pg = pgClients.client }, reply) {
19
+ const { id } = params;
20
+ const limit = Math.min(parseInt(query.limit, 10) || 20, 100);
21
+ const offset = parseInt(query.offset, 10) || 0;
22
+ const search = query.search?.trim() || '';
23
+
24
+ if (!id) return reply.status(400).send({ message: 'service_id is required' });
25
+
26
+ const searchFilter = search
27
+ ? `and (
28
+ coalesce(b.sur_name,'') || ' ' || coalesce(b.user_name,'') || ' ' || coalesce(b.father_name,'') ilike $2
29
+ or b.login ilike $2
30
+ or b.uid::text ilike $2
31
+ )`
32
+ : '';
33
+
34
+ const sql = `
35
+ select
36
+ a.user_id,
37
+ a.actions,
38
+ coalesce(
39
+ nullif(
40
+ coalesce(b.sur_name,'') || coalesce(' ' || b.user_name,'') || coalesce(' ' || b.father_name,''),
41
+ ''
42
+ ),
43
+ b.login,
44
+ b.uid::text,
45
+ a.user_id
46
+ ) as user_full_name
47
+ from gis.services_users_rel a
48
+ left join admin.users b on b.uid = a.user_id
49
+ where a.service_id = $1
50
+ ${searchFilter}
51
+ order by user_full_name
52
+ limit ${limit} offset ${offset}
53
+ `;
54
+
55
+ const countSql = `
56
+ select count(*)::int as total
57
+ from gis.services_users_rel a
58
+ left join admin.users b on b.uid = a.user_id
59
+ where a.service_id = $1
60
+ ${searchFilter}
61
+ `;
62
+
63
+ const args = search ? [id, `%${search}%`] : [id];
64
+
65
+ const [{ rows }, countResult] = await Promise.all([
66
+ pg.query(sql, args),
67
+ pg.query(countSql, args).then((r) => r.rows?.[0]?.total ?? 0),
68
+ ]);
69
+
70
+ return reply.status(200).send({
71
+ rows,
72
+ count: countResult,
73
+ });
74
+ }
@@ -0,0 +1,53 @@
1
+ import path from "node:path";
2
+
3
+ import mapnik from "../../../plugins/mapnik/funcs/mapnik.js";
4
+ import { downloadFile } from "@opengis/fastify-table/utils.js";
5
+
6
+ const { ConvertVector } = mapnik();
7
+
8
+ const ALLOWED_FORMAT = new Set(["zip", "shp", "kml", "gml", "geojson", "json"]);
9
+
10
+ /**
11
+ * Конвертує векторний файл на сервері у GeoJSON через mapnik ConvertVector.
12
+ *
13
+ * @method POST /gis-convert
14
+ * @body {string} filepath - шлях до файлу на сервері
15
+ * @body {number} [srid=4326] - цільовий EPSG код
16
+ */
17
+ export default async function gisConvert({ body = {} }, reply) {
18
+ const { filepath, srid = 4326 } = body;
19
+
20
+ if (!filepath || typeof filepath !== "string") {
21
+ return reply
22
+ .status(400)
23
+ .send({ error: "Потрібен параметр filepath", code: 400 });
24
+ }
25
+
26
+ const ext = path.extname(filepath).replace(".", "").toLowerCase();
27
+ if (!ALLOWED_FORMAT.has(ext)) {
28
+ return reply.status(400).send({
29
+ error: `Непідтримуване розширення: .${ext}. Підтримується: ${[...ALLOWED_FORMAT].join(", ")}`,
30
+ code: 400,
31
+ });
32
+ }
33
+
34
+ const data = await downloadFile(filepath, { buffer: true });
35
+ if (!data) {
36
+ return reply
37
+ .status(404)
38
+ .send({ error: `Файл не знайдено: ${filepath}`, code: 404 });
39
+ }
40
+
41
+ const res = await ConvertVector({
42
+ data,
43
+ format: ext,
44
+ srs: +srid,
45
+ filename: path.basename(filepath),
46
+ }).catch((err) => ({ err }));
47
+
48
+ if (res?.err) {
49
+ return reply.code(500).send(res.err);
50
+ }
51
+
52
+ return reply.send(JSON.parse(res.geojson));
53
+ }
@@ -0,0 +1,86 @@
1
+ const GEOMETRY_TYPES = new Set([
2
+ 'Point',
3
+ 'MultiPoint',
4
+ 'LineString',
5
+ 'MultiLineString',
6
+ 'Polygon',
7
+ 'MultiPolygon',
8
+ 'GeometryCollection',
9
+ ]);
10
+
11
+ const MAX_FILE_SIZE = 50 * 1024 * 1024;
12
+
13
+ function normalizeToFeatureCollection(input) {
14
+ if (!input || typeof input !== 'object') return null;
15
+
16
+ const { type } = input;
17
+
18
+ if (type === 'FeatureCollection' && Array.isArray(input.features)) {
19
+ return {
20
+ type: 'FeatureCollection',
21
+ features: input.features.filter((feature) => feature?.geometry),
22
+ };
23
+ }
24
+
25
+ if (type === 'Feature' && input.geometry) {
26
+ return {
27
+ type: 'FeatureCollection',
28
+ features: [input],
29
+ };
30
+ }
31
+
32
+ if (typeof type === 'string' && GEOMETRY_TYPES.has(type)) {
33
+ return {
34
+ type: 'FeatureCollection',
35
+ features: [{
36
+ type: 'Feature',
37
+ geometry: input,
38
+ properties: {},
39
+ }],
40
+ };
41
+ }
42
+
43
+ return null;
44
+ }
45
+
46
+ /**
47
+ * Парсить GeoJSON з тексту файлу (клієнтський шар, без збереження).
48
+ *
49
+ * @method POST
50
+ * @param {string} body.content - вміст файлу
51
+ * @param {string} [body.filename] - ім'я файлу
52
+ */
53
+ export default async function parseGeoFile({ body = {} }, reply) {
54
+ const { content, filename = '' } = body;
55
+
56
+ if (!content || typeof content !== 'string') {
57
+ return reply.status(400).send({ error: 'Потрібен вміст файлу (content)' });
58
+ }
59
+
60
+ if (content.length > MAX_FILE_SIZE) {
61
+ return reply.status(400).send({ error: 'Максимальний розмір файлу — 50MB' });
62
+ }
63
+
64
+ const lowerName = String(filename).toLowerCase();
65
+ if (lowerName && !lowerName.endsWith('.json') && !lowerName.endsWith('.geojson')) {
66
+ return reply.status(400).send({ error: 'Підтримуються лише файли GeoJSON (.geojson, .json)' });
67
+ }
68
+
69
+ let parsed;
70
+ try {
71
+ parsed = JSON.parse(content);
72
+ }
73
+ catch {
74
+ return reply.status(400).send({ error: 'Некоректний JSON' });
75
+ }
76
+
77
+ const normalized = normalizeToFeatureCollection(parsed);
78
+ if (!normalized?.features?.length) {
79
+ return reply.status(400).send({ error: 'Не вдалося розпізнати геометрію GeoJSON' });
80
+ }
81
+
82
+ return reply
83
+ .status(200)
84
+ .header('Content-Type', 'application/json')
85
+ .send(normalized);
86
+ }