@opengis/gis 0.1.81 → 0.2.0

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 (39) hide show
  1. package/dist/index.css +1 -1
  2. package/dist/index.js +5752 -6487
  3. package/dist/index.umd.cjs +39 -55
  4. package/module/gis/table/gis.group_list.table.json +36 -0
  5. package/module/test/cls/ts.temp_structure.ts_class.json +50 -0
  6. package/module/test/layer/bp.json +60 -0
  7. package/module/test/map/address/addr.yml +6 -0
  8. package/module/test/map/address/index.yml +22 -0
  9. package/module/test/map/address/street.yml +16 -0
  10. package/module/test/map/address2/addr.json +9 -0
  11. package/module/test/map/address2/index.json +35 -0
  12. package/module/test/map/address2/street.json +19 -0
  13. package/module/test/map/address3.yml +52 -0
  14. package/module/test/map/address4.json +34 -0
  15. package/module/test/map/bp_myo.json +37 -0
  16. package/module/test/map/main.json +44 -0
  17. package/module/test/map/mbd.json +91 -0
  18. package/module/test/map/ts.json +20 -129
  19. package/module/test/table/data_address.street.table.json +70 -0
  20. package/package.json +13 -11
  21. package/server/migrations/services.sql +3 -0
  22. package/server/migrations/widgets.sql +21 -0
  23. package/server/plugins/mapnik/funcs/map.proto +241 -241
  24. package/server/routes/gis/index.mjs +2 -0
  25. package/server/routes/gis/registers/funcs/classifiers.js +18 -3
  26. package/server/routes/gis/registers/funcs/handleRegistryRequest.js +4 -2
  27. package/server/routes/gis/services/add.service.js +51 -16
  28. package/server/routes/gis/services/get.services.js +28 -5
  29. package/server/routes/gis/services/legend.auto.js +78 -0
  30. package/server/routes/map/controllers/mapFormat.js +19 -10
  31. package/server/routes/map/controllers/marker_icon.js +2 -1
  32. package/server/routes/map/index.mjs +36 -15
  33. package/server/routes/map/maps/add.map.js +42 -0
  34. package/server/routes/map/maps/del.map.js +19 -0
  35. package/server/routes/map/maps/get.map.js +65 -0
  36. package/server/routes/map/vtile1.js +13 -4
  37. package/server/routes/map/widgets/add.widget.js +39 -0
  38. package/server/routes/map/widgets/del.widget.js +23 -0
  39. package/server/routes/map/widgets/get.widget.js +41 -0
@@ -0,0 +1,78 @@
1
+ import { dataUpdate, getMeta, getSelectVal, yml2json } from "@opengis/fastify-table/utils.js";
2
+
3
+ export default async function legendAuto({ params = {}, query = {}, user = {}, pg = pgClients.client }, reply) {
4
+ if (!params.id) {
5
+ return reply.status(400).send({ error: "not enough params: id", code: 400 });
6
+ }
7
+
8
+ const service = await pg.query(`SELECT source_path as table, style, legend as dblegend FROM gis.services where service_id=$1`, [params.id]).then(el => el.rows?.[0]);
9
+
10
+ if (!service) {
11
+ return reply.status(404).send({ error: "Service not found", code: 404 });
12
+ }
13
+
14
+ const { style, dblegend, table } = service;
15
+
16
+ if (!table) {
17
+ return reply.status(400).send({ error: "service table not specified", code: 400 });
18
+ }
19
+
20
+ if (dblegend && !query.nocache) {
21
+ return reply.status(400).send({ error: "legend already exists", code: 400 });
22
+ }
23
+
24
+ if (!style) {
25
+ return reply.status(400).send({ error: "empty service style", code: 400 });
26
+ }
27
+
28
+ const { colorAttr, iconAttr } = yml2json(style);
29
+
30
+ if (!colorAttr && !iconAttr) {
31
+ return reply.status(400).send({ error: "colorAttr / iconAttr not specified", code: 400 });
32
+ }
33
+
34
+ const { columns = [] } = await getMeta({ pg, table });
35
+
36
+ const attr = columns.find(col => col.name === (colorAttr || iconAttr));
37
+
38
+ if (!attr?.name) {
39
+ return reply.status(404).send({ error: "attribute column not found", code: 404 });
40
+ }
41
+
42
+ const values = await pg.query(`select array_agg(distinct "${attr.name}") from ${table}`).then(el => el.rows?.[0]?.array_agg || []);
43
+ const arr = await getSelectVal({ pg, name: `${table}.${attr.name}`, table, values, ar: true });
44
+
45
+ const colors = {
46
+ 1: "#FFCF1F1F",
47
+ 2: "#FF1FCF2D",
48
+ 3: "#FF1F77CF"
49
+ };
50
+
51
+ const colorKeys = Object.keys(colors).map(Number);
52
+
53
+ const legend = arr
54
+ ? arr.map((el, idx) => ({
55
+ name: el.id,
56
+ label: el.text,
57
+ icon: iconAttr ? el.icon : undefined,
58
+ color: colorAttr ? (el.color || colors[colorKeys[idx % colorKeys.length]]) : undefined,
59
+ }))
60
+ : values.map((el, idx) => ({
61
+ name: el,
62
+ label: el,
63
+ icon: undefined,
64
+ color: colorAttr ? (colors[colorKeys[idx % colorKeys.length]]) : undefined,
65
+ }));
66
+
67
+ const res = await dataUpdate({
68
+ pg,
69
+ table: 'gis.services',
70
+ id: params.id,
71
+ data: {
72
+ legend
73
+ },
74
+ uid: user.uid,
75
+ });
76
+
77
+ return reply.status(200).send(res);
78
+ }
@@ -37,9 +37,9 @@ export default async function mapFormat(req, reply) {
37
37
  }
38
38
  }
39
39
 
40
- let source_path, attributes, tpl, cardInterface;
40
+ let source_path, tpl, cardInterface, card, template;
41
41
  if (service) {
42
- ({ source_path, attributes, interface: cardInterface } = service);
42
+ ({ source_path, interface: cardInterface, card, template } = service);
43
43
  } else {
44
44
  // git
45
45
  tpl = await getTemplate('map', map);
@@ -48,11 +48,12 @@ export default async function mapFormat(req, reply) {
48
48
  return reply.status(404).send('template not found');
49
49
  };
50
50
  source_path = matchedLayer.source_path;
51
- attributes = matchedLayer.card;
51
+ card = matchedLayer.card;
52
+ template = matchedLayer.template;
52
53
 
53
54
  }
54
55
 
55
- const columns = attributes;
56
+ const columns = card;
56
57
  if (!source_path) return reply.status(404).send('missing source_path');
57
58
 
58
59
  const pk = pg.pk?.[source_path];
@@ -83,32 +84,40 @@ export default async function mapFormat(req, reply) {
83
84
  if (excluded.includes(col.name)) continue;
84
85
  if (col.hidden_card === true) continue;
85
86
 
86
- const name = col.ua || col.name;
87
+ const name = col.label || col.ua || col.name;
87
88
  let value;
88
89
  value = fullRow[`${col.name}_data`]?.text
89
90
  || fullRow[`${col.name}_text`]
90
- || fullRow[col.name]
91
- || '-';
91
+ || fullRow[col.name] ;
92
92
 
93
93
  if (col.format === 'date' && fullRow[col.name]) {
94
94
  // const dt = formatDate(fullRow[col.name], { hash: { format: 'dd.mm.yy' } });
95
95
  const dt = await handlebars.compile(`{{formatDate "${fullRow[col.name]}"}}`)({ format: 'dd.mm.yy' });
96
96
  value = dt;
97
97
  }
98
-
98
+ if (!value) continue;
99
99
  result.push(`<div class="grid grid-cols-1 gap-1 py-3 sm:grid-cols-3 sm:gap-4 even:bg-gray-50 text-[12px]">
100
100
  <dt class="text-gray-900">${name}</dt>
101
- <dd class="text-gray-700 sm:col-span-2">${value}</dd>
101
+ <dd class="text-gray-700 sm:col-span-2">${value|| '-'}</dd>
102
102
  </div>`);
103
103
  }
104
104
 
105
- const html = `<dl class="divide-y divide-gray-100 py-[5px]">${result.join('')}</dl>`;
105
+ const htmlTemplate = pg?.pk?.['admin.template']
106
+ ? await pg.query('select body from admin.templates where name=$1 limit 1').then(el => el.rows?.[0]?.body)
107
+ : null;
108
+
109
+ const html1 = await handlebars.compile(htmlTemplate || 'template not found')(fullRow);
110
+
111
+ const html = template
112
+ ? html1
113
+ : `<dl class="divide-y divide-gray-100 py-[5px]">${result.join('')}</dl>`;
106
114
 
107
115
  const res = {
108
116
  time: Date.now() - time,
109
117
  id,
110
118
  rows: fullRow,
111
119
  columns,
120
+ template,
112
121
  html
113
122
  };
114
123
  if (service) {
@@ -31,7 +31,8 @@ export default async function markerIconApi(req, reply) {
31
31
  const iconPath = path.join(fileIconDir, params?.data);
32
32
 
33
33
  if (fs.existsSync(iconPath)) {
34
- return fs.createReadStream(iconPath);
34
+ const buffer = fs.readFileSync(iconPath); // works for small files same as stream, but also unit-test-friendly
35
+ return reply.type('image/png').send(buffer);
35
36
  }
36
37
 
37
38
  await downloadImage(`https://data.softpro.ua/api-user/marker/${params.data}`, iconPath).catch(err => {
@@ -13,10 +13,18 @@ import mapFormat from './controllers/mapFormat.js';
13
13
  import maps from './controllers/maps.js';
14
14
  import markerIconApi from './controllers/marker_icon.js';
15
15
 
16
+ import getMap from './maps/get.map.js';
17
+ import addMap from './maps/add.map.js';
18
+ import delMap from './maps/del.map.js';
19
+
20
+ import getWidget from './widgets/get.widget.js';
21
+ import addWidget from './widgets/add.widget.js';
22
+ import delWidget from './widgets/del.widget.js';
23
+
16
24
  const schemaInfo = {
17
25
  type: 'object',
18
26
  properties: {
19
- params: {
27
+ publicParams: {
20
28
  slug: {
21
29
  type: 'string',
22
30
  },
@@ -37,29 +45,42 @@ const schemaInfo = {
37
45
  },
38
46
  };
39
47
 
40
- const policy = ['public'];
48
+ const publicParams = { config: { policy: 'L0' }, schema: schemaInfo }; // * L0 === public
49
+ const privilegedParams = { config: { policy: 'L1', role: 'admin' }, schema: schemaInfo }; // ? just auth or admin
41
50
 
42
51
  export default async function route(app) {
43
- app.get('/maps', { config: { policy }, schema: schemaInfo }, maps);
44
- app.get('/map/:slug', { config: { policy }, schema: schemaInfo }, map);
45
- app.get('/map-tiles/:slug/:z/:y/:x', { config: { policy }, schema: schemaInfo }, mapTiles);
46
- app.get('/map-features/:slug/:id', { config: { policy }, schema: schemaInfo }, mapFeatures);
47
- app.get('/map-features-point/:slug', { config: { policy }, schema: schemaInfo }, mapFeatures);
52
+ app.get('/gis-map/:id?', publicParams, getMap);
53
+ app.post('/gis-map/:id?', privilegedParams, addMap);
54
+ app.put('/gis-map/:id', privilegedParams, addMap);
55
+ app.delete('/gis-map/:id', privilegedParams, delMap);
56
+
57
+ app.get('/gis-widget/:map/:id?', publicParams, getWidget);
58
+ app.post('/gis-widget/:map/:id?', privilegedParams, addWidget);
59
+ app.put('/gis-widget/:map/:id', privilegedParams, addWidget);
60
+ app.delete('/gis-widget/:map/:id', privilegedParams, delWidget);
61
+
62
+ app.get('/maps', publicParams, maps);
63
+ app.get('/map/:slug', publicParams, map);
64
+ app.get('/map-tiles/:slug/:z/:y/:x', publicParams, mapTiles);
65
+ app.get('/map-features/:slug/:id', publicParams, mapFeatures);
66
+ app.get('/map-features-point/:slug', publicParams, mapFeatures);
48
67
 
49
- app.get('/map-catalog', { config: { policy }, schema: schemaInfo }, mapCatalog);
50
- app.get('/map-catalog/:service/:attr', { config: { policy }, schema: schemaInfo }, mapCatalogAttribute);
51
- app.get('/layer-rtile/:id/:z/:y/:x', { config: { policy }, schema: {} }, rtile);
52
- app.get('/layer-vtile/:id/:z/:y/:x', { config: { policy }, schema: schemaInfo }, vtile);
68
+ app.get('/map-catalog', publicParams, mapCatalog);
69
+ app.get('/map-catalog/:service/:attr', publicParams, mapCatalogAttribute);
70
+ app.get('/layer-rtile/:id/:z/:y/:x', publicParams, rtile);
71
+ app.get('/layer-vtile/:id/:z/:y/:x', publicParams, vtile);
53
72
 
54
73
  if (!app.hasRoute({ method: 'GET', url: '/api/vtile/:layer/:lang/:z/:y/:x', })) {
55
74
  console.log("\x1b[34m%s\x1b[0m", 'add vtile from gis');
56
- app.get('/vtile/:layer/:lang/:z/:y/:x', { config: { policy }, schema: schemaInfo }, vtile1);
75
+ app.get('/vtile/:layer/:lang/:z/:y/:x', publicParams, vtile1);
57
76
  }
58
77
  if (!app.hasRoute({ method: 'GET', url: '/api/gis-layer-list', })) {
59
78
  console.log("\x1b[34m%s\x1b[0m", 'add gis-layer-list from gis');
60
- app.get('/gis-layer-list', { config: { policy }, }, layerList);
79
+ app.get('/gis-layer-list', publicParams, layerList);
61
80
  }
62
81
 
63
- app.get('/marker-icon/*', { config: { policy }, schema: {} }, markerIconApi);
64
- app.get('/map-format', { config: { policy }, schema: {} }, mapFormat);
82
+ app.get('/gis-icon/*', publicParams, markerIconApi);
83
+ app.get('/icon/*', publicParams, markerIconApi);
84
+ app.get('/marker-icon/*', publicParams, markerIconApi);
85
+ app.get('/map-format', publicParams, mapFormat);
65
86
  }
@@ -0,0 +1,42 @@
1
+ import { dataInsert, dataUpdate, pgClients } from "@opengis/fastify-table/utils.js";
2
+ export default async function addMap({
3
+ method, params = {}, body, pg = pgClients.client, user = {},
4
+ }, reply) {
5
+ const { uid } = user;
6
+
7
+ if (!uid) {
8
+ return reply.status(401).send('unauthorized');
9
+ }
10
+
11
+ if (method === 'POST') {
12
+ if (!body?.name) {
13
+ return reply.status(400).send('not enough body params: name');
14
+ }
15
+
16
+ if (!body?.map_key) {
17
+ return reply.status(400).send('not enough body params: map_key');
18
+ }
19
+
20
+ const { rows = [] } = await dataInsert({
21
+ pg,
22
+ id: params.id,
23
+ table: 'gis.maps',
24
+ data: body,
25
+ uid,
26
+ });
27
+ return reply.status(200).send(rows[0]);
28
+ }
29
+
30
+ if (!params.id) {
31
+ return reply.status(400).send('not enough params: id');
32
+ }
33
+
34
+ const row = await dataUpdate({
35
+ pg,
36
+ id: params.id,
37
+ table: 'gis.maps',
38
+ data: body,
39
+ uid,
40
+ });
41
+ return reply.status(200).send(row);
42
+ }
@@ -0,0 +1,19 @@
1
+ import { dataDelete, pgClients } from "@opengis/fastify-table/utils.js";
2
+
3
+ export default async function delMap({
4
+ params = {}, pg = pgClients.client, user = {},
5
+ }, reply) {
6
+ const { uid } = user;
7
+
8
+ if (!uid) {
9
+ return reply.status(401).send('unauthorized');
10
+ }
11
+
12
+ const row = await dataDelete({
13
+ pg,
14
+ id: params.id,
15
+ table: 'gis.maps',
16
+ uid,
17
+ });
18
+ return reply.status(200).send(row);
19
+ }
@@ -0,0 +1,65 @@
1
+ import path from 'node:path';
2
+ import yaml from 'js-yaml';
3
+ import { getMeta, getTemplate, pgClients, getTemplates, getTemplateSync } from "@opengis/fastify-table/utils.js";
4
+
5
+ const table = 'gis.maps';
6
+
7
+ const columnType = {
8
+ text: 'text',
9
+ date: 'date',
10
+ bool: 'yes/no',
11
+ numeric: 'number',
12
+ integer: 'number',
13
+ 'timestamp without time zone': 'date',
14
+ 'timestamp with time zone': 'date',
15
+ };
16
+
17
+ export default async function getMap({ params = {}, pg = pgClients.client }, reply) {
18
+ const { columns = [] } = await getMeta({ pg, table }) || {};
19
+
20
+ const fields = columns.map(({ name, dataTypeID, title }) => ({ name, type: columnType[pg.pgType?.[dataTypeID] || 'text'], label: title || name }));
21
+ const mapList = getTemplates('map').map(el => el[0]);
22
+
23
+ const rows = pg.pk?.['gis.maps'] ? await pg.query(
24
+ `SELECT map_id as id, * FROM gis.maps where ${params.id ? 'map_id=$1' : '1=1'}`,
25
+ [params.id].filter(Boolean),
26
+ ).then(el => el.rows || []) : [];
27
+
28
+ if (params.id && !mapList.includes(params.id) && !rows[0]) {
29
+ return { status: 404, message: 'map not found'}
30
+ }
31
+
32
+ if (params.id) {
33
+ const map = rows[0] || {};
34
+ const loadTemplate = await getTemplate('map', params.id) || await getTemplate('map', map.map_key);
35
+
36
+ const obj = loadTemplate;
37
+
38
+ if (Array.isArray(obj?.layers) && typeof obj?.layers?.[0] === 'string') {
39
+ const layers = await Promise.all(obj.layers.map(async (layer) => {
40
+ const layerData = await getTemplate('layer', layer);
41
+ const serviceData = layerData || await pg.query('select service_id as id, * from gis.services where service_id=$1', [layer]).then(el => el.rows?.[0]);
42
+ serviceData.style = yaml.load(serviceData.style);
43
+ return { ...serviceData, id: layer,visible:true };
44
+ }));
45
+ return { ...rows[0],...obj, layers };
46
+ }
47
+
48
+ return reply.status(200).send({ ...rows[0], ...loadTemplate });
49
+ }
50
+
51
+ if (params.id && !rows.length) {
52
+ return reply.status(404).send('map not found');
53
+ }
54
+
55
+ if (params.id) {
56
+ return reply.status(200).send({ ...rows[0], fields });
57
+ }
58
+
59
+ maps.filter(el => Object.keys(el).length > 1).forEach(el => rows.push(el));
60
+ return reply.status(200).send({
61
+ template,
62
+ rows,
63
+ fields,
64
+ });
65
+ }
@@ -5,6 +5,7 @@ const headers = {
5
5
  import Sphericalmercator from '@mapbox/sphericalmercator';
6
6
  const mercator = new Sphericalmercator({ size: 256 });
7
7
  import { getTemplate, getMeta, getFilterSQL } from '@opengis/fastify-table/utils.js';
8
+ import yaml from 'js-yaml';
8
9
 
9
10
  export default async function vtile({ params = {}, query, pg, user }, reply) {
10
11
  const { y, z } = params;
@@ -21,10 +22,18 @@ export default async function vtile({ params = {}, query, pg, user }, reply) {
21
22
  const mapData = map ? await getTemplate('map', map) : {};
22
23
  const mapStyle = mapData?.widgets?.find?.(el => el.type === 'attribute')?.config?.layer?.style;
23
24
 
24
- const geom = data1?.geometry_column || 'geom';
25
- const table = data?.table_name || data1?.source_path;
26
- const style = mapStyle || data?.style || data1?.style;
27
- const filterList = data?.filter_list || data1?.filters;
25
+ const geom = data?.geometry_column
26
+ || data1?.geometry_column
27
+ || 'geom';
28
+ const table = data?.source_path
29
+ || data?.table_name
30
+ || data1?.source_path;
31
+ const style = mapStyle
32
+ || data?.style
33
+ || (data1?.style ? yaml.load(data1?.style) : null);
34
+ const filterList = data?.filters
35
+ || data?.filter_list
36
+ || data1?.filters;
28
37
  const layerQuery = data?.query || data1?.query;
29
38
 
30
39
  // bbox
@@ -0,0 +1,39 @@
1
+ import { dataInsert, dataUpdate, pgClients } from "@opengis/fastify-table/utils.js";
2
+ export default async function addWidget({
3
+ method, params = {}, body, pg = pgClients.client, user = {},
4
+ }, reply) {
5
+ const { uid } = user;
6
+
7
+ if (!uid) {
8
+ return reply.status(401).send('unauthorized');
9
+ }
10
+
11
+ if (!params.map) {
12
+ return reply.status(400).send('not enough params: map id');
13
+ }
14
+
15
+ if (method === 'PUT' && !params.id) {
16
+ return reply.status(400).send('not enough params: id');
17
+ }
18
+
19
+ if (method === 'POST') {
20
+ Object.assign(body, { map_id: params.map });
21
+ const { rows = [] } = await dataInsert({
22
+ pg,
23
+ id: params.id,
24
+ table: 'gis.widgets',
25
+ data: body,
26
+ uid,
27
+ });
28
+ return reply.status(200).send(rows[0]);
29
+ }
30
+
31
+ const row = await dataUpdate({
32
+ pg,
33
+ id: params.id,
34
+ table: 'gis.widgets',
35
+ data: body,
36
+ uid,
37
+ });
38
+ return reply.status(200).send(row);
39
+ }
@@ -0,0 +1,23 @@
1
+ import { dataDelete, pgClients } from "@opengis/fastify-table/utils.js";
2
+
3
+ export default async function delWidget({
4
+ params = {}, pg = pgClients.client, user = {},
5
+ }, reply) {
6
+ const { uid } = user;
7
+
8
+ if (!uid) {
9
+ return reply.status(401).send('unauthorized');
10
+ }
11
+
12
+ if (!params.map) {
13
+ return reply.status(400).send('not enough params: map id');
14
+ }
15
+
16
+ const row = await dataDelete({
17
+ pg,
18
+ id: params.id,
19
+ table: 'gis.widgets',
20
+ uid,
21
+ });
22
+ return reply.status(200).send(row);
23
+ }
@@ -0,0 +1,41 @@
1
+ import { getMeta, pgClients } from "@opengis/fastify-table/utils.js";
2
+
3
+ const table = 'gis.widgets';
4
+
5
+ const columnType = {
6
+ text: 'text',
7
+ date: 'date',
8
+ bool: 'yes/no',
9
+ numeric: 'number',
10
+ integer: 'number',
11
+ 'timestamp without time zone': 'date',
12
+ 'timestamp with time zone': 'date',
13
+ };
14
+
15
+ export default async function getWidget({ params = {}, pg = pgClients.client }, reply) {
16
+ if (!params.map) {
17
+ return reply.status(400).send('not enough params: map id');
18
+ }
19
+
20
+ const { columns = [] } = await getMeta({ pg, table }) || {};
21
+
22
+ const fields = columns.map(({ name, dataTypeID, title }) => ({ name, type: columnType[pg.pgType?.[dataTypeID] || 'text'], label: title || name }));
23
+
24
+ const rows = pg.pk?.['gis.maps'] && pg.pk?.['gis.widgets'] ? await pg.query(
25
+ `SELECT widget_id as id, * FROM gis.widgets where map_id=$1 and ${params.id ? 'widget_id=$2' : '1=1'}`,
26
+ [params.map, params.id].filter(Boolean),
27
+ ).then(el => el.rows || []) : [];
28
+
29
+ if (params.id && !rows.length) {
30
+ return reply.status(404).send('widget not found');
31
+ }
32
+
33
+ if (params.id) {
34
+ return reply.status(200).send({ ...rows[0], fields });
35
+ }
36
+
37
+ return reply.status(200).send({
38
+ rows,
39
+ fields,
40
+ });
41
+ }