@opengis/gis 0.0.17 → 0.0.19
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/import-file.cjs +845 -0
- package/dist/{index.css → import-file.css} +1 -1
- package/dist/{index.js → import-file.js} +13002 -11703
- package/module/gis/card/gis.maps.table/index.yml +11 -0
- package/module/gis/card/gis.maps.table/main_info.hbs +25 -0
- package/module/gis/card/gis.maps.table/maps_layers.hbs +19 -0
- package/module/gis/card/gis.metadata.table/index.yml +19 -0
- package/module/gis/card/gis.metadata.table/main_info.hbs +21 -0
- package/module/gis/card/gis.metadata.table/metadata_info.hbs +28 -0
- package/module/gis/card/gis.metadata.table/other.hbs +26 -0
- package/module/gis/card/gis.rasters.table/index.yml +8 -0
- package/module/gis/card/gis.rasters.table/main_info.hbs +27 -0
- package/module/gis/card/gis.registers.table/cls.hbs +36 -0
- package/module/gis/card/gis.registers.table/columns.hbs +83 -0
- package/module/gis/card/gis.registers.table/filters.hbs +80 -0
- package/module/gis/card/gis.registers.table/index.yml +21 -0
- package/module/gis/card/gis.registers.table/main_info.hbs +35 -0
- package/module/gis/card/gis.registers.table/source.hbs +45 -0
- package/module/gis/card/gis.services.table/attributes.hbs +85 -0
- package/module/gis/card/gis.services.table/filters.hbs +83 -0
- package/module/gis/card/gis.services.table/index.yml +20 -0
- package/module/gis/card/gis.services.table/main_info.hbs +27 -0
- package/module/gis/card/gis.services.table/source.hbs +25 -0
- package/module/gis/cls/bool.yes_no.json +12 -0
- package/module/gis/cls/encoding.json +14 -0
- package/module/gis/cls/geom_type.json +14 -0
- package/module/gis/cls/gis.column_type.json +34 -0
- package/module/gis/cls/gis.column_view_type.json +18 -0
- package/module/gis/cls/gis.filter_type.json +22 -0
- package/module/gis/cls/language.json +10 -0
- package/module/gis/cls/meta.service_type.json +43 -0
- package/module/gis/cls/ogc.service.json +22 -0
- package/module/gis/cls/service_type.json +42 -0
- package/module/gis/cls/source_type.json +10 -0
- package/module/gis/cls/standarts.json +6 -0
- package/module/gis/cls/topic_category.json +107 -0
- package/module/gis/cls/update_frequency.json +30 -0
- package/module/gis/form/gis.group_list.form.json +17 -0
- package/module/gis/form/gis.maps.form.json +67 -0
- package/module/gis/form/gis.metadata.form.json +240 -0
- package/module/gis/form/gis.ogc_service.form.json +36 -0
- package/module/gis/form/gis.rasters.form.json +100 -0
- package/module/gis/form/gis.registers.form.json +264 -0
- package/module/gis/form/gis.registers_column.form.json +77 -0
- package/module/gis/form/gis.registers_filter.form.json +65 -0
- package/module/gis/form/gis.services.form.json +300 -0
- package/module/gis/form/gis.services_attributes.form.json +68 -0
- package/module/gis/form/gis.services_filter.form.json +65 -0
- package/module/gis/menu.json +43 -0
- package/module/gis/select/gis.group_list.sql +1 -0
- package/module/gis/select/pg.columns.parent.sql +6 -0
- package/module/gis/select/pg.table_name.sql +17 -0
- package/module/gis/select/service_id.sql +1 -0
- package/module/gis/table/gis.maps.table.json +78 -0
- package/module/gis/table/gis.metadata.table.json +71 -0
- package/module/gis/table/gis.ogc_service.table.json +81 -0
- package/module/gis/table/gis.rasters.table.json +98 -0
- package/module/gis/table/gis.registers.table.json +79 -0
- package/module/gis/table/gis.services.table.json +105 -0
- package/module/gis/table/site.gis.registers.table.json +75 -0
- package/module/gis/table/site.gis.services.table.json +104 -0
- package/module/gis/templates/ISO19136_2017_gml_template.xml +331 -0
- package/module/gis/tokens.yml +5 -0
- package/package.json +11 -5
- package/plugin.js +12 -0
- package/server/plugins/mapnik/funcs/createXML.js +72 -0
- package/server/plugins/mapnik/funcs/gdalWrapper.js +72 -0
- package/server/plugins/mapnik/funcs/mapnik.js +101 -0
- package/server/plugins/mapnik/funcs/rasterConfig.js +11 -0
- package/server/plugins/mapnik/funcs/rasterExists.js +21 -0
- package/server/plugins/mapnik/funcs/rasterInfo.js +109 -0
- package/server/plugins/mapnik/funcs/rasterVrt.js +56 -0
- package/server/plugins/mapnik/funcs/rasterXML.js +65 -0
- package/server/plugins/mapnik/funcs/rootFolder.mjs +8 -0
- package/server/plugins/mapnik/index.mjs +12 -0
- package/server/plugins/mapnik/utils/map.proto +241 -0
- package/server/routes/gis/index.mjs +19 -0
- package/server/routes/gis/metadata/metadataXML.js +13 -0
- package/server/routes/gis/registers/funcs/classifiers.js +26 -0
- package/server/routes/gis/registers/funcs/columns.js +5 -0
- package/server/routes/gis/registers/funcs/handleRegistryRequest.js +100 -0
- package/server/routes/gis/registers/gis.registry.js +32 -0
- package/server/routes/gis/registers/gis.registry.list.js +59 -0
- package/server/routes/gis/registers/insert.columns.js +107 -0
- package/server/routes/gis/registers/insert.filters.js +110 -0
- package/server/routes/gis/registers/map.registry.js +79 -0
- package/server/routes/gis/services/get.layer.geom.js +27 -0
- package/server/routes/map/controllers/map.js +123 -0
- package/server/routes/map/controllers/mapCatalog.js +55 -0
- package/server/routes/map/controllers/mapCatalogAttribute.js +56 -0
- package/server/routes/map/controllers/mapFeatures.js +120 -0
- package/server/routes/map/controllers/mapFormat.js +111 -0
- package/server/routes/map/controllers/mapTiles.js +148 -0
- package/server/routes/map/controllers/maps.js +16 -0
- package/server/routes/map/controllers/marker_icon.js +42 -0
- package/server/routes/map/controllers/rtile.js +133 -0
- package/server/routes/map/controllers/vtile.js +146 -0
- package/server/routes/map/index.mjs +51 -0
- package/server/routes/root.mjs +3 -0
- package/utils.js +9 -0
- package/dist/index.umd.cjs +0 -845
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { pgClients, getTemplatePath, getTemplate } from "@opengis/fastify-table/utils.js";
|
|
2
|
+
const pg = pgClients.client;
|
|
3
|
+
|
|
4
|
+
export default async function gisRegistryList(req = {}) {
|
|
5
|
+
const { query = {} } = req;
|
|
6
|
+
const { page = 1, limit = 9, search = '' } = query;
|
|
7
|
+
const offset = limit * (page - 1);
|
|
8
|
+
|
|
9
|
+
const dbResult = await pg.query(
|
|
10
|
+
`SELECT register_key, name, description, keywords
|
|
11
|
+
FROM gis.registers
|
|
12
|
+
WHERE is_public = true AND is_active = true`
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
let templates = dbResult?.rows;
|
|
16
|
+
let fromDatabase = true;
|
|
17
|
+
|
|
18
|
+
if (!templates?.length) {
|
|
19
|
+
fromDatabase = false;
|
|
20
|
+
|
|
21
|
+
const registryListTemplate = await getTemplatePath('registry');
|
|
22
|
+
if (!registryListTemplate?.length) throw new Error('Templates not found');
|
|
23
|
+
|
|
24
|
+
templates = ( await Promise.all(
|
|
25
|
+
registryListTemplate.map(async ([slug]) => {
|
|
26
|
+
try {
|
|
27
|
+
const tpl = await getTemplate('registry', slug);
|
|
28
|
+
if (tpl?.visible === false) return null;
|
|
29
|
+
|
|
30
|
+
const { register_key, name, description, keywords } = tpl;
|
|
31
|
+
return { register_key, name, description, keywords };
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const filteredBySearch = search
|
|
40
|
+
? templates.filter(tpl => {
|
|
41
|
+
const q = search.toLowerCase();
|
|
42
|
+
return (
|
|
43
|
+
tpl.name?.toLowerCase().includes(q) ||
|
|
44
|
+
tpl.description?.toLowerCase().includes(q) ||
|
|
45
|
+
tpl.keywords?.join(' ').toLowerCase().includes(q)
|
|
46
|
+
);
|
|
47
|
+
})
|
|
48
|
+
: templates;
|
|
49
|
+
|
|
50
|
+
const paged = filteredBySearch.slice(offset, offset + limit);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
rows: paged,
|
|
54
|
+
total: filteredBySearch.length,
|
|
55
|
+
page: parseInt(page, 10),
|
|
56
|
+
limit: parseInt(limit, 10),
|
|
57
|
+
offset
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { getToken } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
/**
|
|
3
|
+
* Автозаповнення колонок Реєстру
|
|
4
|
+
*
|
|
5
|
+
* @method put
|
|
6
|
+
* @summary Автозаповнення колонок Реєстру
|
|
7
|
+
* @alias gis.registers
|
|
8
|
+
* @type api
|
|
9
|
+
* @tag dataset
|
|
10
|
+
* @param {String} token - токен
|
|
11
|
+
* @returns {Number} status - номер помилки
|
|
12
|
+
* @returns {String} error - опис помилки
|
|
13
|
+
* @returns {String} message - повідомлення про успішне виконання і передача певних даних
|
|
14
|
+
*/
|
|
15
|
+
export default async function insertColumns(req,reply) {
|
|
16
|
+
const {pg, user, params} = req;
|
|
17
|
+
const token = params.token;
|
|
18
|
+
if (!token) return { message: 'empty token', status: 400 };
|
|
19
|
+
try {
|
|
20
|
+
|
|
21
|
+
const tokenData = await getToken({ token, uid: user?.uid });
|
|
22
|
+
if (!tokenData) return reply.code(400).send({ message: 'access restricted', status: 400 });
|
|
23
|
+
|
|
24
|
+
const { id, type } = JSON.parse(tokenData) || {};
|
|
25
|
+
if (!id||!type) return reply.code(403).send({ message: 'access restricted', status: 403 });
|
|
26
|
+
if (!['services','registers'].includes(type)) return reply.code(403).send({ message: 'invalid type', status: 403 });
|
|
27
|
+
|
|
28
|
+
const tableColumn = (type === 'registers') ? 'table_name':'source_path';
|
|
29
|
+
const jsonColumn = (type === 'registers') ? 'columns':'attributes';
|
|
30
|
+
const pk = (type === 'registers')?'register_id':'service_id';
|
|
31
|
+
|
|
32
|
+
const select = `update gis.${type} set ${jsonColumn}=
|
|
33
|
+
(select
|
|
34
|
+
jsonb_agg(row_to_json(q)) as column_list
|
|
35
|
+
from
|
|
36
|
+
(select
|
|
37
|
+
q.column_name as name,
|
|
38
|
+
case
|
|
39
|
+
when q.column_name='editor_id' and comment is null then 'Хто редагував'
|
|
40
|
+
when q.column_name ilike '%geom%' and comment is null then 'Геометрія'
|
|
41
|
+
when q.column_name='editor_date' and comment is null then 'Дата редагування'
|
|
42
|
+
when q.column_name='image' and comment is null then 'Зображення'
|
|
43
|
+
when q.column_name='file' and comment is null then 'Файл'
|
|
44
|
+
when q.column_name='files' and comment is null then 'Файли'
|
|
45
|
+
else comment
|
|
46
|
+
end as ua,
|
|
47
|
+
case
|
|
48
|
+
when q.data_type in ('text','character varying') then 'text'
|
|
49
|
+
when q.data_type ='ARRAY' then 'tags'
|
|
50
|
+
when q.data_type in ('integer','numeric','double precision') then 'number'
|
|
51
|
+
when q.data_type ='boolean' then 'yes/no'
|
|
52
|
+
when q.data_type='USER-DEFINED' then 'geom'
|
|
53
|
+
when q.data_type in ('date','timestamp without time zone') then 'date'
|
|
54
|
+
end as format,
|
|
55
|
+
'' as data,
|
|
56
|
+
false as hidden_register,
|
|
57
|
+
false as hidden_card
|
|
58
|
+
from
|
|
59
|
+
(
|
|
60
|
+
select
|
|
61
|
+
column_name,
|
|
62
|
+
data_type,
|
|
63
|
+
table_schema || '.' || table_name as table_name
|
|
64
|
+
from
|
|
65
|
+
(
|
|
66
|
+
select
|
|
67
|
+
distinct table_name,
|
|
68
|
+
table_schema,
|
|
69
|
+
column_name,
|
|
70
|
+
data_type
|
|
71
|
+
from
|
|
72
|
+
information_schema.columns
|
|
73
|
+
) q
|
|
74
|
+
) q
|
|
75
|
+
|
|
76
|
+
left join lateral (
|
|
77
|
+
SELECT
|
|
78
|
+
pgd.description AS comment,
|
|
79
|
+
pgns.nspname||'.'||pgc.relname AS table_name,
|
|
80
|
+
pg_attribute.attname AS column_name
|
|
81
|
+
|
|
82
|
+
FROM
|
|
83
|
+
pg_catalog.pg_description pgd
|
|
84
|
+
JOIN pg_catalog.pg_class pgc ON pgd.objoid = pgc.oid
|
|
85
|
+
JOIN pg_catalog.pg_namespace pgns ON pgc.relnamespace = pgns.oid
|
|
86
|
+
JOIN pg_catalog.pg_attribute pg_attribute ON pgc.oid = pg_attribute.attrelid
|
|
87
|
+
AND pg_attribute.attnum = pgd.objsubid
|
|
88
|
+
)b on b.column_name=q.column_name and b.table_name=q.table_name
|
|
89
|
+
where
|
|
90
|
+
q.table_name = (
|
|
91
|
+
select
|
|
92
|
+
${tableColumn}
|
|
93
|
+
from
|
|
94
|
+
gis.${type}
|
|
95
|
+
where
|
|
96
|
+
${pk} = '${id}'
|
|
97
|
+
) and q.column_name not in ('cdate','uid') and q.data_type<>'json')q
|
|
98
|
+
)
|
|
99
|
+
where ${pk} = '${id}'
|
|
100
|
+
`;
|
|
101
|
+
await pg.one(select);
|
|
102
|
+
|
|
103
|
+
return { message: 'Колонки заповнені упішно', status: 200 };
|
|
104
|
+
} catch (err) {
|
|
105
|
+
return reply.code(500).send({ message: err.message, status: 500 });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { getToken } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Автозаповнення фільтрів Реєстру
|
|
5
|
+
*
|
|
6
|
+
* @method put
|
|
7
|
+
* @summary Автозаповнення фільтрів Реєстру
|
|
8
|
+
* @alias createDatasetFromApplicationApi
|
|
9
|
+
* @type api
|
|
10
|
+
* @tag dataset
|
|
11
|
+
* @param {String} token - токен Реєстру
|
|
12
|
+
* @returns {Number} status - номер помилки
|
|
13
|
+
* @returns {String} error - опис помилки
|
|
14
|
+
* @returns {String} message - повідомлення про успішне виконання і передача певних даних
|
|
15
|
+
* @returns {String} headers - заголовки HTTP
|
|
16
|
+
*/
|
|
17
|
+
export default async function insertFilters(req,reply) {
|
|
18
|
+
const {
|
|
19
|
+
pg, user, params,
|
|
20
|
+
} =req;
|
|
21
|
+
const token = params.token ;
|
|
22
|
+
if (!token) return { message: 'empty token', status: 400 };
|
|
23
|
+
try {
|
|
24
|
+
|
|
25
|
+
const tokenData = await getToken({ token, uid: user?.uid });
|
|
26
|
+
if (!tokenData) return reply.code(400).send({ message: 'access restricted', status: 400 });
|
|
27
|
+
|
|
28
|
+
const { id, type } = JSON.parse(tokenData) || {};
|
|
29
|
+
|
|
30
|
+
if (!id||!type) return reply.code(403).send({ message: 'access restricted', status: 403 });
|
|
31
|
+
if (!['services','registers'].includes(type)) return reply.code(403).send({ message: 'invalid type', status: 403 });
|
|
32
|
+
|
|
33
|
+
const tableColumn = (type === 'registers') ? 'table_name':'source_path';
|
|
34
|
+
const pk = (type === 'registers')?'register_id':'service_id';
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
const select = `update gis.${type} set filters=
|
|
38
|
+
(select
|
|
39
|
+
jsonb_agg(row_to_json(q)) as filter_list
|
|
40
|
+
from
|
|
41
|
+
(select
|
|
42
|
+
q.column_name as id,
|
|
43
|
+
case
|
|
44
|
+
when q.column_name='editor_id' and comment is null then 'Хто редагував'
|
|
45
|
+
when q.column_name ilike '%geom%' and comment is null then 'Геометрія'
|
|
46
|
+
when q.column_name='editor_date' and comment is null then 'Дата редагування'
|
|
47
|
+
when q.column_name='image' and comment is null then 'Зображення'
|
|
48
|
+
when q.column_name='file' and comment is null then 'Файл'
|
|
49
|
+
when q.column_name='files' and comment is null then 'Файли'
|
|
50
|
+
else comment
|
|
51
|
+
end as label,
|
|
52
|
+
case
|
|
53
|
+
when q.data_type in ('text','character varying','integer','numeric','double precision') then 'Text'
|
|
54
|
+
when q.data_type ='ARRAY' then 'Tags'
|
|
55
|
+
when q.data_type ='boolean' then 'Switcher'
|
|
56
|
+
when q.data_type in ('date','timestamp without time zone') then 'Date'
|
|
57
|
+
--else q.data_type
|
|
58
|
+
end as type,
|
|
59
|
+
'' as data,
|
|
60
|
+
false as disabled
|
|
61
|
+
from
|
|
62
|
+
(
|
|
63
|
+
select
|
|
64
|
+
column_name,
|
|
65
|
+
data_type,
|
|
66
|
+
table_schema || '.' || table_name as table_name
|
|
67
|
+
from
|
|
68
|
+
(
|
|
69
|
+
select
|
|
70
|
+
distinct table_name,
|
|
71
|
+
table_schema,
|
|
72
|
+
column_name,
|
|
73
|
+
data_type
|
|
74
|
+
from
|
|
75
|
+
information_schema.columns
|
|
76
|
+
) q
|
|
77
|
+
) q
|
|
78
|
+
|
|
79
|
+
left join lateral (
|
|
80
|
+
SELECT
|
|
81
|
+
pgd.description AS comment,
|
|
82
|
+
pgns.nspname||'.'||pgc.relname AS table_name,
|
|
83
|
+
pg_attribute.attname AS column_name
|
|
84
|
+
|
|
85
|
+
FROM
|
|
86
|
+
pg_catalog.pg_description pgd
|
|
87
|
+
JOIN pg_catalog.pg_class pgc ON pgd.objoid = pgc.oid
|
|
88
|
+
JOIN pg_catalog.pg_namespace pgns ON pgc.relnamespace = pgns.oid
|
|
89
|
+
JOIN pg_catalog.pg_attribute pg_attribute ON pgc.oid = pg_attribute.attrelid
|
|
90
|
+
AND pg_attribute.attnum = pgd.objsubid
|
|
91
|
+
)b on b.column_name=q.column_name and b.table_name=q.table_name
|
|
92
|
+
where
|
|
93
|
+
q.table_name = (
|
|
94
|
+
select
|
|
95
|
+
${tableColumn}
|
|
96
|
+
from
|
|
97
|
+
gis.${type}
|
|
98
|
+
where
|
|
99
|
+
${pk} = '${id}'
|
|
100
|
+
) and q.column_name not in ('cdate','uid','geom') and q.data_type not in ('json','USER-DEFINED'))q
|
|
101
|
+
)
|
|
102
|
+
where ${pk} = '${id}'
|
|
103
|
+
`;
|
|
104
|
+
await pg.one(select);
|
|
105
|
+
|
|
106
|
+
return { message: 'Фільтри заповнені упішно', status: 200 };
|
|
107
|
+
} catch (err) {
|
|
108
|
+
return reply.code(500).send({ message: err.message, status: 500 });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { pgClients, getFilterSQL, getTemplate } from "@opengis/fastify-table/utils.js";
|
|
2
|
+
import { attachClassifiers } from './funcs/classifiers.js';
|
|
3
|
+
const pg = pgClients.client;
|
|
4
|
+
|
|
5
|
+
export default async function mapRegistry(req,reply) {
|
|
6
|
+
const { params = {}, query = {} } = req;
|
|
7
|
+
const { slug, id } = params;
|
|
8
|
+
const { filter, page = 1 } = query;
|
|
9
|
+
|
|
10
|
+
if (!slug || !id) return reply.code(404).send({ message: 'Params slug and id is required', status: 404 });
|
|
11
|
+
|
|
12
|
+
const mapTemplate = await getTemplate('map', slug);
|
|
13
|
+
if (!mapTemplate) return reply.code(404).send({ message: 'Template not found', status: 404 });
|
|
14
|
+
|
|
15
|
+
const listWidget = mapTemplate.widgets?.find(w => w.type === 'list' && w.visible !== false && w.id === id);
|
|
16
|
+
if (!listWidget) return reply.code(404).send({ message: 'List widget not found', status: 404 });
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
table,
|
|
20
|
+
pk,
|
|
21
|
+
query: baseQuery = '1=1',
|
|
22
|
+
order,
|
|
23
|
+
config = {},
|
|
24
|
+
name = '',
|
|
25
|
+
} = listWidget;
|
|
26
|
+
|
|
27
|
+
const { columns = [], filters = [], limit: configLimit = 20 } = config;
|
|
28
|
+
const limit = query.limit || configLimit;
|
|
29
|
+
const offset = limit * (page - 1);
|
|
30
|
+
const selectColumns = columns.map(col => col.name);
|
|
31
|
+
|
|
32
|
+
const classifiers = columns
|
|
33
|
+
.filter(col => col.data && ["select", "badge", "tags"].includes(col.format))
|
|
34
|
+
.map(col => ({ name: col.name, classifier: col.data }));
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
const { q: sqlFilter } = filter
|
|
38
|
+
? await getFilterSQL({ table, filter, json: 1 })
|
|
39
|
+
: { q: '1=1' };
|
|
40
|
+
|
|
41
|
+
const whereConditions = [baseQuery, sqlFilter].filter(Boolean).join(" AND ");
|
|
42
|
+
const whereClause = whereConditions ? `WHERE ${whereConditions}` : "";
|
|
43
|
+
|
|
44
|
+
const sqlBase = `FROM ${table} ${whereClause}`;
|
|
45
|
+
const sqlOrder = order ? `ORDER BY ${order}` : "";
|
|
46
|
+
const sqlLimit = `LIMIT $1 OFFSET $2`;
|
|
47
|
+
const sqlSelect = `SELECT ${pk} as id, ${selectColumns.join(", ")}
|
|
48
|
+
${sqlBase}`;
|
|
49
|
+
const dataQuery = `${sqlSelect} ${sqlOrder} ${sqlLimit}`;
|
|
50
|
+
const totalQuery = `SELECT COUNT(*) ${sqlBase}`;
|
|
51
|
+
|
|
52
|
+
const [dataRes, countRes] = await Promise.all([
|
|
53
|
+
pg.query(dataQuery, [limit, offset]),
|
|
54
|
+
pg.query(totalQuery)
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
const rows = await attachClassifiers(dataRes?.rows, classifiers);
|
|
58
|
+
const total = parseInt(countRes?.rows[0]?.count || 0, 10);
|
|
59
|
+
|
|
60
|
+
const listViewConfig = {};
|
|
61
|
+
columns.forEach(col => {
|
|
62
|
+
if (col.view_type === 'title') listViewConfig.title = col?.name;
|
|
63
|
+
if (col.view_type === 'subtitle') listViewConfig.subtitle = col?.name;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
name,
|
|
68
|
+
rows,
|
|
69
|
+
total,
|
|
70
|
+
columns,
|
|
71
|
+
filters,
|
|
72
|
+
config: {
|
|
73
|
+
...listWidget,
|
|
74
|
+
list: {
|
|
75
|
+
...listViewConfig
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default async function getLayerGeom(req) {
|
|
2
|
+
const { pg, params, query } = req;
|
|
3
|
+
|
|
4
|
+
const { id } = params;
|
|
5
|
+
if (!id) return { message: 'id is empty', status: 403 };
|
|
6
|
+
|
|
7
|
+
const selectTable = `select source_path,geometry_column from gis.services where service_id='${id}'`;
|
|
8
|
+
const { source_path: table, geometry_column: geometryColumn = 'geom' } = await pg.one(selectTable);
|
|
9
|
+
if (!table) return;
|
|
10
|
+
const selectGeom = `
|
|
11
|
+
SELECT 'FeatureCollection' As type,
|
|
12
|
+
json_agg(q) as features
|
|
13
|
+
FROM (
|
|
14
|
+
SELECT 'Feature' as type,
|
|
15
|
+
row_number() over() as id, st_asgeojson(
|
|
16
|
+
st_force2d(${geometryColumn}),
|
|
17
|
+
6,
|
|
18
|
+
0
|
|
19
|
+
) :: json as geometry
|
|
20
|
+
from ${table}
|
|
21
|
+
where ${geometryColumn} is not null) q`;
|
|
22
|
+
if (query?.sql) return selectGeom;
|
|
23
|
+
const { rows } = await pg.query(selectGeom);
|
|
24
|
+
|
|
25
|
+
return { message: rows, status: 200 };
|
|
26
|
+
|
|
27
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
|
|
6
|
+
import { config, getMeta, getTemplate, getTemplateSync, pgClients, getSelect, getSelectVal, getTemplatePath } from '@opengis/fastify-table/utils.js';
|
|
7
|
+
|
|
8
|
+
const { prefix = '/api' } = config;
|
|
9
|
+
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
|
|
11
|
+
const maps = getTemplatePath('map')?.map?.(el => {
|
|
12
|
+
const { name = el[0] } = getTemplateSync('map', el[0]) || {};
|
|
13
|
+
return { slug: el[0], name };
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
async function processObject(loadTemplate, pg = pgClients.client, unittest) {
|
|
17
|
+
const { bbox = [], center = [] } = loadTemplate?.source_path && pg.pk?.[loadTemplate.source_path]
|
|
18
|
+
? await pg.query(`select
|
|
19
|
+
array[ST_XMin(env), ST_YMin(env), ST_XMax(env),ST_YMax(env)] as bbox,
|
|
20
|
+
st_asgeojson(st_centroid(env))::json->'coordinates' as center
|
|
21
|
+
from ( select st_extent(geom) as env from ${loadTemplate.source_path} where ${loadTemplate.source_query || '1=1'})q`
|
|
22
|
+
).then(el => el.rows?.[0] || {})
|
|
23
|
+
: {};
|
|
24
|
+
loadTemplate.map = { bbox, center };
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if (!loadTemplate?.widgets?.length) return;
|
|
28
|
+
// add legend if not exists in widget
|
|
29
|
+
const idx = loadTemplate.widgets.findIndex(el => el.type === 'attribute_style');
|
|
30
|
+
const { source_path: attrTable, attribute, cls: clsName, legend = [], colors } = loadTemplate.widgets.find(el => el.type === 'attribute_style')?.config || {};
|
|
31
|
+
const colors1 = colors || ['red', 'black', 'blue', 'green', 'orange'];
|
|
32
|
+
|
|
33
|
+
if (!legend?.length && attribute && attrTable) {
|
|
34
|
+
const { columns = {} } = await getMeta({ pg, table: attrTable });
|
|
35
|
+
const { dataTypeID = 0 } = columns.find(item => item.name === attribute) || {};
|
|
36
|
+
const q = pg.pgType?.[dataTypeID]?.includes?.('[]')
|
|
37
|
+
? `select unnest(${attribute})::text as id,count(*) from ${attrTable} group by unnest(${attribute}) limit 100`
|
|
38
|
+
: `select ${attribute}::text as id,count(*) from ${attrTable} group by ${attribute} limit 100`;
|
|
39
|
+
|
|
40
|
+
if (config.trace || unittest) console.log(q);
|
|
41
|
+
const countArr = await pg.queryCache(q, { time: 5 });
|
|
42
|
+
|
|
43
|
+
const ids = countArr.rows.map(item => item.id);
|
|
44
|
+
|
|
45
|
+
const cls = await getSelect(clsName, pg);
|
|
46
|
+
const clsData = await getSelectVal({ pg, values: ids, cls: clsName });
|
|
47
|
+
|
|
48
|
+
const options = countArr.rows.map((cel, idx) => {
|
|
49
|
+
const data = cls?.arr?.find?.(c => c.id?.toString?.() === cel.id?.toString?.()) || { text: clsData?.[cel.id]?.text || clsData?.[cel.id] };
|
|
50
|
+
const color = (colors?.length ? colors1[idx % colors1.length] : null) || data?.color || colors1[idx % colors1.length];
|
|
51
|
+
return { ...cel, ...data, color };
|
|
52
|
+
});
|
|
53
|
+
loadTemplate.widgets[idx].config.legend = options;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
const { source_path: table, filters = [] } = loadTemplate.widgets.find(el => el.type === 'filters')?.config || {};
|
|
58
|
+
|
|
59
|
+
filters.forEach(el => Object.assign(el, { name: el.name || el.id }));
|
|
60
|
+
|
|
61
|
+
// md5 hash for suggest security
|
|
62
|
+
filters.filter(el => el.type?.toLowerCase() === 'select').map(async (el) => {
|
|
63
|
+
const hash = createHash('md5').update([table, el.name].join()).digest('hex');
|
|
64
|
+
el.api = `${prefix}/suggest/hash-${hash}`;
|
|
65
|
+
const filepath = path.join(dirname, `../../../../log/suggest/${hash}.json`);
|
|
66
|
+
if (existsSync(filepath)) { return; };
|
|
67
|
+
mkdirSync(path.dirname(filepath), { recursive: true });
|
|
68
|
+
writeFileSync(filepath, JSON.stringify({ table, column: el.name }), { encoding: 'utf8' });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// add options to filters
|
|
72
|
+
if (filters.filter(el => el.data).length && table) {
|
|
73
|
+
const { columns = {} } = await getMeta({ pg, table });
|
|
74
|
+
await Promise.all(filters.filter(el => el.data && el.name).map(async (el) => {
|
|
75
|
+
const { dataTypeID = 0 } = columns.find(item => item.name === el.name) || {};
|
|
76
|
+
const q = pg.pgType?.[dataTypeID]?.includes?.('[]')
|
|
77
|
+
? `select unnest(${el.name})::text as id,count(*) from ${table} group by unnest(${el.name}) limit 100`
|
|
78
|
+
: `select ${el.name}::text as id,count(*) from ${table} group by ${el.name} limit 100`;
|
|
79
|
+
|
|
80
|
+
if (config.trace || unittest) console.log(q);
|
|
81
|
+
const countArr = await pg.queryCache(q, { time: 5 });
|
|
82
|
+
|
|
83
|
+
const ids = countArr.rows.map(item => item.id);
|
|
84
|
+
|
|
85
|
+
const cls = await getSelect(el.data, pg);
|
|
86
|
+
const clsData = await getSelectVal({ pg, values: ids, name: el.data });
|
|
87
|
+
|
|
88
|
+
const options = countArr.rows.map(cel => {
|
|
89
|
+
const data = cls?.arr?.find?.(c => c.id?.toString?.() === cel.id?.toString?.()) || { text: clsData[cel.id]?.text || clsData[cel.id] };
|
|
90
|
+
return { ...cel, ...data };
|
|
91
|
+
});
|
|
92
|
+
Object.assign(el, { options });
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
return loadTemplate;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export default async function map({ pg = pgClients.client, params = {}, unittest }, reply) {
|
|
99
|
+
const { slug } = params;
|
|
100
|
+
|
|
101
|
+
const loadTemplate = await getTemplate('map', slug);
|
|
102
|
+
|
|
103
|
+
if (!loadTemplate) {
|
|
104
|
+
return reply.status(404).send('map not found');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!Array.isArray(loadTemplate)) {
|
|
108
|
+
await processObject(loadTemplate, pg, unittest);
|
|
109
|
+
return { ...loadTemplate, maps };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const index = loadTemplate?.find?.(el => el[0].split('.').shift() === 'index')?.[1];
|
|
113
|
+
|
|
114
|
+
if (!index) {
|
|
115
|
+
return reply.status(404).send('map index not found');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
await processObject(index, pg, unittest);
|
|
119
|
+
|
|
120
|
+
const layers = loadTemplate?.filter?.(el => el[0].split('.').shift() !== 'index')?.map(el => ({ ...el[1], id: el[0].replace('.yml', '').replace('.json', '') }));
|
|
121
|
+
|
|
122
|
+
return reply.status(200).send({ ...index, layers, maps });
|
|
123
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { config, pgClients } from "@opengis/fastify-table/utils.js";
|
|
2
|
+
|
|
3
|
+
const { prefix = '/api' } = config;
|
|
4
|
+
|
|
5
|
+
function getBBox(coordinates) {
|
|
6
|
+
return coordinates[0].reduce(
|
|
7
|
+
([minLon, minLat, maxLon, maxLat], [lon, lat]) => [
|
|
8
|
+
Math.min(minLon, lon),
|
|
9
|
+
Math.min(minLat, lat),
|
|
10
|
+
Math.max(maxLon, lon),
|
|
11
|
+
Math.max(maxLat, lat)
|
|
12
|
+
],
|
|
13
|
+
[coordinates[0][0][0], coordinates[0][0][1], coordinates[0][0][0], coordinates[0][0][1]]
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default async function mapCatalog({ pg = pgClients.client }, reply) {
|
|
18
|
+
const { rows: services = [] } = pg.pk?.['gis.services']
|
|
19
|
+
? await pg.query(`select
|
|
20
|
+
service_id as id, name, service_type, service_url as url,
|
|
21
|
+
source_type, source_path, style, layers,
|
|
22
|
+
st_asgeojson(bbox)::json->'coordinates' as bbox,
|
|
23
|
+
st_asgeojson(center)::json->'coordinates' as center
|
|
24
|
+
from gis.services where is_active and is_public`
|
|
25
|
+
)
|
|
26
|
+
: {};
|
|
27
|
+
|
|
28
|
+
services.filter(el => el.bbox).forEach(el => {
|
|
29
|
+
el.bbox = getBBox(el.bbox);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
services.filter(el => el.source_type === 'database').forEach(el => {
|
|
33
|
+
el.url = `${prefix}/layer-vtile/${el.id}/{z}/{x}/{y}.vmt`;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const { rows: rasters = [] } = pg.pk?.['gis.rasters']
|
|
37
|
+
? await pg.query(
|
|
38
|
+
`select
|
|
39
|
+
raster_id as id, name, source_type,
|
|
40
|
+
st_asgeojson(bbox):: json -> 'coordinates' as bbox,
|
|
41
|
+
st_asgeojson(center):: json -> 'coordinates' as center
|
|
42
|
+
from gis.rasters where is_active and is_public`
|
|
43
|
+
)
|
|
44
|
+
: {};
|
|
45
|
+
|
|
46
|
+
rasters.forEach(el => {
|
|
47
|
+
el.url = `${prefix}/layer-rtile/${el.id}/{z}/{x}/{y}.png`;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
rasters.filter(el => el.bbox).forEach(el => {
|
|
51
|
+
el.bbox = getBBox(el.bbox);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return reply.status(200).send({ services, rasters });
|
|
55
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { pgClients, getMeta, getSelect, getSelectVal } from "@opengis/fastify-table/utils.js";
|
|
2
|
+
|
|
3
|
+
export default async function mapCatalogAttribute({ pg = pgClients.client, params = {}, query = {} }, reply) {
|
|
4
|
+
const { service, attr } = params;
|
|
5
|
+
if (!service) {
|
|
6
|
+
return reply.status(404).send('not enough params: service');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (!attr) {
|
|
10
|
+
return reply.status(404).send('not enough params: attr');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!pg.pk?.['gis.services']) {
|
|
14
|
+
return reply.status(404).send('services table not found');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const source = await pg.query(
|
|
18
|
+
'select source_path from gis.services where service_id=$1 and is_active and is_public',
|
|
19
|
+
[params.service]
|
|
20
|
+
).then(el => el.rows?.[0]?.source_path);
|
|
21
|
+
|
|
22
|
+
if (!source) {
|
|
23
|
+
return reply.status(404).send('service not found');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const cls = await getSelect(query.cls || attr, pg);
|
|
27
|
+
const colname = query.cls ? attr : attr.split(':').pop();
|
|
28
|
+
|
|
29
|
+
if (!colname) {
|
|
30
|
+
return reply.status(400).send('invalid params: attr');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { columns = [] } = await getMeta({ pg, table: source });
|
|
34
|
+
const { dataTypeID } = columns.find(el => el.name === colname) || {};
|
|
35
|
+
|
|
36
|
+
const q = pg.pgType[dataTypeID]?.includes('[]')
|
|
37
|
+
? `select unnest(${colname})::text as id,count(*) from ${source} group by unnest(${colname}) limit 100`
|
|
38
|
+
: `select ${colname}::text as id,count(*) from ${source} group by ${colname} limit 100`;
|
|
39
|
+
|
|
40
|
+
const countArr = await pg.queryCache(q);
|
|
41
|
+
|
|
42
|
+
const ids = countArr.rows.map(el => el.id);
|
|
43
|
+
|
|
44
|
+
const clsData = await getSelectVal({ pg, values: ids, name: query.cls || attr });
|
|
45
|
+
|
|
46
|
+
const legend = countArr.rows.map(cel => {
|
|
47
|
+
const data = cls?.arr?.find(c => c.id?.toString?.() === cel.id?.toString?.()) || { text: clsData?.[cel.id]?.text || clsData?.[cel.id] };
|
|
48
|
+
return { ...cel, ...data };
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const { min, max } = await pg.queryCache(
|
|
52
|
+
`select min(${attr}), max(${attr}) from ${source} where 1=1`,
|
|
53
|
+
).then(el => { return el.rows?.[0] || {}; });
|
|
54
|
+
|
|
55
|
+
return reply.status(200).send({ legend, min, max });
|
|
56
|
+
}
|