@opengis/gis 0.2.85 → 0.2.87
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/index.css +1 -1
- package/dist/index.js +6371 -6069
- package/dist/index.umd.cjs +73 -63
- package/package.json +1 -1
- package/server/migrations/cartocss.sql +4 -0
- package/server/migrations/rasters.sql +3 -1
- package/server/plugins/mapnik/map.proto +1 -0
- package/server/routes/gis/cartocss/add.cartocss.js +1 -0
- package/server/routes/gis/cartocss/get.cartocss.js +8 -1
- package/server/routes/map/controllers/mapFormat.js +162 -26
- package/server/routes/mapnik/functions/cartoBounds.js +1 -1
package/package.json
CHANGED
|
@@ -19,3 +19,7 @@ create table if not exists gis.cartocss(
|
|
|
19
19
|
|
|
20
20
|
alter table gis.cartocss add column if not exists geom public.geometry;
|
|
21
21
|
alter table gis.cartocss add column if not exists source_path text;
|
|
22
|
+
alter table gis.cartocss add column if not exists card_auto boolean not null default false;
|
|
23
|
+
alter table gis.cartocss add column if not exists card_html text;
|
|
24
|
+
alter table gis.cartocss add column if not exists card_table text;
|
|
25
|
+
alter table gis.cartocss add column if not exists card_columns text;
|
|
@@ -261,4 +261,6 @@ COMMENT ON COLUMN gis.rasters.raster_zoom IS 'Зум для відображен
|
|
|
261
261
|
COMMENT ON COLUMN gis.rasters.srid IS 'Просторова система координат';
|
|
262
262
|
COMMENT ON COLUMN gis.rasters.center IS 'Центр покриття';
|
|
263
263
|
COMMENT ON COLUMN gis.rasters.bbox IS 'Область покриття сервісу';
|
|
264
|
-
COMMENT ON COLUMN gis.rasters.group_id IS 'id групи';
|
|
264
|
+
COMMENT ON COLUMN gis.rasters.group_id IS 'id групи';
|
|
265
|
+
|
|
266
|
+
ALTER TABLE gis.rasters add COLUMN if not exists isadmin boolean NOT NULL default false;
|
|
@@ -7,6 +7,8 @@ const { RenderTile } = mapnik();
|
|
|
7
7
|
|
|
8
8
|
const { prefix = '/api' } = config;
|
|
9
9
|
|
|
10
|
+
const exclude = ['created_at', 'updated_at', 'created_by', 'updated_by'];
|
|
11
|
+
|
|
10
12
|
export default async function checkCarto({
|
|
11
13
|
pg = pgClients.client, params,
|
|
12
14
|
}, reply) {
|
|
@@ -15,12 +17,16 @@ export default async function checkCarto({
|
|
|
15
17
|
return reply.status(400).send({ error: 'mapnik server address needed', code: 400 });
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
// extract all columns, migrations-agnostic
|
|
21
|
+
const cartocss = await pg.query('select *, ARRAY[ST_XMin(geom), ST_YMin(geom), ST_XMax(geom), ST_YMax(geom)] as bounds, st_asgeojson(geom)::json as geom from gis.cartocss where cartocss_id=$1', [params.id]).then(el => el.rows?.[0]);
|
|
19
22
|
|
|
20
23
|
if (!cartocss) {
|
|
21
24
|
return reply.status(404).send({ error: `cartocss not found: ${params.id}`, code: 404 });
|
|
22
25
|
}
|
|
23
26
|
|
|
27
|
+
// filter system columns
|
|
28
|
+
Object.keys(cartocss).filter(key => exclude.includes(key)).forEach(key => delete cartocss[key]);
|
|
29
|
+
|
|
24
30
|
const rtile = await RenderTile({
|
|
25
31
|
name: params.id,
|
|
26
32
|
width: 256,
|
|
@@ -34,6 +40,7 @@ export default async function checkCarto({
|
|
|
34
40
|
return {
|
|
35
41
|
time: Date.now() - time,
|
|
36
42
|
...cartocss,
|
|
43
|
+
info: !!(cartocss.card_auto || (cartocss.card_html && cartocss.card_table)),
|
|
37
44
|
url: `${prefix}/gis-rtile/${params.id}/{z}/{x}/{y}.png`,
|
|
38
45
|
bounds,
|
|
39
46
|
render: !!rtile.base64,
|
|
@@ -1,43 +1,181 @@
|
|
|
1
1
|
import {
|
|
2
2
|
pgClients, getTemplate, handlebars, metaFormat,
|
|
3
|
+
getMeta,
|
|
3
4
|
} from '@opengis/fastify-table/utils.js';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
const { pg = pgClients.client, query = {}, user } = req;
|
|
7
|
-
const { layer, id } = query;
|
|
8
|
-
const time = Date.now();
|
|
6
|
+
const excluded = ['created_by', 'created_at', 'updated_by', 'geom', 'id', 'uid', 'updated_at', 'cdate', 'editor_date', 'editor_id'];
|
|
9
7
|
|
|
10
|
-
|
|
8
|
+
function getLayerTableQuery({
|
|
9
|
+
el, id, point, srids, nogeom,
|
|
10
|
+
}, pg = pgClients.client) {
|
|
11
|
+
const {
|
|
12
|
+
gcol: geom = 'geom', table, pk = pg.pk[el.table], query: tableQuery, srid, columns,
|
|
13
|
+
} = el;
|
|
14
|
+
const step = srids.length && srids.includes(srid - 0) && srid !== 4326 ? 10 : 0.001;
|
|
15
|
+
return `SELECT
|
|
16
|
+
${pk} as "id"
|
|
17
|
+
,'${el.key}' as "key"
|
|
18
|
+
${nogeom ? '' : `,st_asgeojson(${srid !== 4326 ? `st_transform(${geom}, 4326)` : geom})::json as geom`}, ${srid !== 4326 ? `st_transform(${geom}, 4326)` : geom}::box2d as box2d, ${columns ? `row_to_json(t)` : 'null'} as "data"
|
|
19
|
+
${point ? `,st_distance(${el.srid !== 4326 ? `st_transform(${geom}, 4326)` : geom},'${point} ') as distance` : ''}
|
|
20
|
+
FROM ${table} t WHERE ${tableQuery || '1 = 1'} and st_isvalid(${geom}) and st_srid(${geom}) > 0
|
|
21
|
+
and ${point ? `case
|
|
22
|
+
when ST_GeometryType(${geom}) in ('ST_Polygon','ST_MultiPolygon')
|
|
23
|
+
then st_intersects(${srid !== 4326 ? `st_transform(${geom}, 4326)` : geom},st_buffer('${point}',${step}))
|
|
24
|
+
|
|
25
|
+
when ST_GeometryType(${geom}) in ('ST_LineString','ST_MultiLineString', 'ST_MultiPoint', 'ST_Point')
|
|
26
|
+
then st_distance(${srid !== 4326 ? `st_transform(${geom}, 4326)` : geom},'${point}') < ${step}
|
|
27
|
+
else false end` : `${id ? `${pk}='${id.replace(/'/g, "")}'` : '2=2'}`
|
|
28
|
+
} ${point ? 'order by distance' : ''} limit 1`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function getLayersData({
|
|
32
|
+
id, lng, lat, cartocss, nogeom,
|
|
33
|
+
}, pg = pgClients.client) {
|
|
34
|
+
if (!(lng && lat) && !id) {
|
|
35
|
+
return { error: 'not enough params: lat / lng' };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!cartocss.config?.length) {
|
|
39
|
+
return { error: 'empty cartocss.config' };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const point = lng && lat ? `srid=4326;point(${lng} ${lat})`.replace(/'/g, "''") : null;
|
|
43
|
+
|
|
44
|
+
const srids = pg?.queryCache ? await pg.queryCache('select json_agg(srid) from public.spatial_ref_sys').then(el => el.rows?.[0]?.json_agg || []) : [];
|
|
45
|
+
|
|
46
|
+
const q = cartocss.card_table
|
|
47
|
+
? getLayerTableQuery({
|
|
48
|
+
el: { key: cartocss.card_table, table: cartocss.card_table, columns: cartocss.card_columns }, id, point, srids, nogeom,
|
|
49
|
+
}, pg)
|
|
50
|
+
: cartocss.config.filter((el, idx, arr) => el.key && el.table && pg.pk?.[el.table] && arr.map(item => item.table).indexOf(el.table) === idx).map(el => getLayerTableQuery({
|
|
51
|
+
el, id, point, srids, nogeom,
|
|
52
|
+
}, pg)).join(' union all ');
|
|
53
|
+
|
|
54
|
+
// console.time(q);
|
|
55
|
+
const rows = await pg.query(q).then(el => el.rows || []);
|
|
56
|
+
// console.timeEnd(q);
|
|
57
|
+
|
|
58
|
+
if (!rows.length) {
|
|
59
|
+
return { error: 'object not found', code: 404 };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const layerData = rows[0].key ? cartocss.config?.find(el => el.key === rows[0].key) || { table: cartocss?.card_table, columns: cartocss?.card_columns } : {};
|
|
11
63
|
|
|
12
|
-
|
|
64
|
+
if (rows[0].data && layerData.columns) {
|
|
65
|
+
const columnNames = layerData.columns.split(',');
|
|
66
|
+
const rowData = Object.keys(rows[0].data).filter(key => columnNames.includes(key)).reduce((acc, key) => ({ ...acc, [key]: rows[0].data[key] }), {});
|
|
67
|
+
Object.assign(rows[0], { data: undefined, ...rowData });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const metaColumns = cartocss?.card_table && cartocss?.card_columns
|
|
71
|
+
? await getMeta({ pg, table: cartocss?.card_table }).then(el => el.columns?.map(({ name }) => ({ name, format: 'text' })) || [])
|
|
72
|
+
: [];
|
|
73
|
+
|
|
74
|
+
const columns = metaColumns.length && cartocss?.card_columns
|
|
75
|
+
? metaColumns.filter(el => cartocss?.card_columns.includes(el.name))
|
|
76
|
+
: Object.keys(rows[0] || {}).map(key => ({ name: key, format: 'text' }));
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
rows,
|
|
80
|
+
columns,
|
|
81
|
+
tname: layerData.table,
|
|
82
|
+
htmlTemplate: cartocss.card_html,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
13
85
|
|
|
86
|
+
async function getTableData({
|
|
87
|
+
service, layer, id, user,
|
|
88
|
+
}, pg = pgClients.client) {
|
|
14
89
|
const {
|
|
15
|
-
source_path: tname,
|
|
16
|
-
} = service;
|
|
90
|
+
source_path: tname, card, template,
|
|
91
|
+
} = service || {};
|
|
17
92
|
|
|
18
93
|
const columns = (card || []).filter((col) => (user?.uid ? true : !col.is_admin));
|
|
19
|
-
|
|
94
|
+
|
|
95
|
+
if (!tname) {
|
|
96
|
+
return { error: 'missing source_path', code: 400 };
|
|
97
|
+
}
|
|
20
98
|
|
|
21
99
|
const pk = pg.pk?.[tname];
|
|
22
|
-
if (!pk) return reply.status(404).send('missing pk');
|
|
23
100
|
|
|
24
|
-
if (!
|
|
101
|
+
if (!pk) {
|
|
102
|
+
return { error: 'missing pk', code: 400 };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!columns) {
|
|
106
|
+
return { error: 'columns setting not found', code: 404 };
|
|
107
|
+
}
|
|
25
108
|
|
|
26
109
|
const columnNames = columns.map(col => col.name);
|
|
27
|
-
|
|
110
|
+
|
|
28
111
|
const filteredColumns = columnNames.filter(name => !excluded.includes(name));
|
|
29
112
|
|
|
30
113
|
const rows = await pg.query(
|
|
31
|
-
`SELECT ${pk} as "id", geom::json, geom::box2d as box2d ${filteredColumns?.length > 0 ? ',' : ''} ${filteredColumns.map((n) => `"${n}"`).join(', ')} FROM ${tname} WHERE ${pk} = $1`,
|
|
114
|
+
`SELECT ${pk} as "id", st_asgeojson(geom)::json as geom, geom::box2d as box2d ${filteredColumns?.length > 0 ? ',' : ''} ${filteredColumns.map((n) => `"${n}"`).join(', ')} FROM ${tname} WHERE ${pk} = $1`,
|
|
32
115
|
[id],
|
|
33
116
|
).then(el => el.rows || []);
|
|
34
117
|
|
|
35
|
-
if (!rows.length)
|
|
118
|
+
if (!rows.length) {
|
|
119
|
+
return { error: 'object not found', code: 404 };
|
|
120
|
+
}
|
|
36
121
|
|
|
37
122
|
const classifiers = columns
|
|
38
123
|
.filter(col => col.data && ['select', 'badge', 'tags'].includes(col.format))
|
|
39
124
|
.reduce((acc, curr) => ({ ...acc, [curr.name]: curr.data }), {});
|
|
40
125
|
|
|
126
|
+
const htmlTemplate = pg?.pk?.['admin.templates'] && service?.card_mode === 'html'
|
|
127
|
+
? await pg.query('select body from admin.templates where name=$1 limit 1', [template || layer]).then(el => el.rows?.[0]?.body)
|
|
128
|
+
: null;
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
rows,
|
|
132
|
+
columns,
|
|
133
|
+
classifiers,
|
|
134
|
+
tname,
|
|
135
|
+
htmlTemplate,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export default async function mapFormat({ pg = pgClients.client, query = {}, user }, reply) {
|
|
140
|
+
const {
|
|
141
|
+
layer, id, lat, lng, nogeom,
|
|
142
|
+
} = query;
|
|
143
|
+
|
|
144
|
+
const t1 = Date.now();
|
|
145
|
+
|
|
146
|
+
if (!layer) {
|
|
147
|
+
return reply.status(400).send({ error: 'not enough query params: layer', code: 400 });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const service = await getTemplate('layer', layer) || (pg.pk?.['gis.services'] ? await pg.query('SELECT * FROM gis.services WHERE service_id = $1', [layer]).then(el => el.rows[0]) : null);
|
|
151
|
+
const cartocss = pg.pk?.['gis.cartocss'] ? await pg.query('SELECT * FROM gis.cartocss WHERE cartocss_id = $1', [layer]).then(el => el.rows[0]) : null;
|
|
152
|
+
|
|
153
|
+
if (!cartocss && !id) {
|
|
154
|
+
return reply.status(400).send({ error: 'not enough query params: id', code: 400 });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (cartocss && !cartocss.config?.length) {
|
|
158
|
+
return reply.status(400).send({ error: 'invalid layer settings: empty config', code: 400 });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (cartocss && !(lat && lng) && !id) {
|
|
162
|
+
return reply.status(400).send({ error: 'not enough query params: lat and lng / id', code: 400 });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const {
|
|
166
|
+
rows, columns, classifiers, tname, template, error, code = 500, htmlTemplate,
|
|
167
|
+
} = cartocss
|
|
168
|
+
? await getLayersData({
|
|
169
|
+
cartocss, id, lng, lat, nogeom,
|
|
170
|
+
}, pg)
|
|
171
|
+
: await getTableData({
|
|
172
|
+
service, layer, id, user, nogeom,
|
|
173
|
+
}, pg);
|
|
174
|
+
|
|
175
|
+
if (error) {
|
|
176
|
+
return reply.status(code).send({ error, code });
|
|
177
|
+
}
|
|
178
|
+
|
|
41
179
|
rows.forEach(row => Object.assign(row, { box2d: undefined, bounds: row.box2d ? row.box2d.replace(/[A-Z\)\(]+/g, '').split(/[ ,]+/).map(el => el - 0) : null }));
|
|
42
180
|
|
|
43
181
|
await metaFormat({
|
|
@@ -51,34 +189,32 @@ export default async function mapFormat(req, reply) {
|
|
|
51
189
|
const col = filtered[i];
|
|
52
190
|
|
|
53
191
|
const name = col.label || col.ua || col.name;
|
|
54
|
-
const value = (col.format === 'date' ? new Date(fullRow[col.name]).toLocaleDateString('uk-UA') : null) || fullRow[`${col.name}_data`]?.text
|
|
55
|
-
|| fullRow[`${col.name}_text`]
|
|
192
|
+
const value = (col.format === 'date' ? new Date(fullRow[col.name]).toLocaleDateString('uk-UA') : null) || fullRow[`${col.name} _data`]?.text
|
|
193
|
+
|| fullRow[`${col.name} _text`]
|
|
56
194
|
|| fullRow[col.name];
|
|
57
195
|
|
|
58
|
-
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]">
|
|
196
|
+
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]" >
|
|
59
197
|
<dt class="text-gray-900">${name}</dt>
|
|
60
198
|
<dd class="text-gray-700 sm:col-span-2">${value || '-'}</dd>
|
|
61
|
-
</div
|
|
199
|
+
</div > `);
|
|
62
200
|
}
|
|
63
201
|
|
|
64
|
-
const htmlTemplate = pg?.pk?.['admin.templates'] && service.card_mode === 'html'
|
|
65
|
-
? await pg.query('select body from admin.templates where name=$1 limit 1', [template || layer]).then(el => el.rows?.[0]?.body)
|
|
66
|
-
: null;
|
|
67
|
-
|
|
68
202
|
const html1 = htmlTemplate ? await handlebars.compile(htmlTemplate)(fullRow) : null;
|
|
69
203
|
|
|
70
|
-
const
|
|
204
|
+
const auto = service ? service?.card_mode === 'list' : cartocss?.card_auto;
|
|
205
|
+
const html = html1 || (auto ? `< dl class="divide-y divide-gray-100 py-[5px]" > ${result.join('')}</dl > ` : undefined);
|
|
71
206
|
|
|
72
207
|
const res = {
|
|
73
|
-
time: Date.now() -
|
|
74
|
-
id,
|
|
208
|
+
time: Date.now() - t1,
|
|
209
|
+
id: fullRow.id,
|
|
75
210
|
rows: fullRow,
|
|
76
211
|
columns,
|
|
77
212
|
template,
|
|
78
213
|
html,
|
|
214
|
+
auto,
|
|
79
215
|
};
|
|
80
216
|
if (service) {
|
|
81
|
-
res.cardInterface = cardInterface;
|
|
217
|
+
res.cardInterface = service.cardInterface;
|
|
82
218
|
}
|
|
83
219
|
|
|
84
220
|
return res;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { pgClients } from "@opengis/fastify-table/utils.js";
|
|
2
2
|
|
|
3
3
|
export default async function cartoBounds({ id, dataset }, pg = pgClients.client) {
|
|
4
|
-
const sqlBounds = dataset.map(el => ({ tbl: el.table.split('.'), geom: el.gcol || 'geom' })).map(({ tbl, geom }) => `select ST_EstimatedExtent('${tbl[0]}','${tbl[1]}', '${geom}')`);
|
|
4
|
+
const sqlBounds = dataset.map(el => ({ tbl: el.table.replace(/"/g, "").split('.'), geom: el.gcol || 'geom' })).map(({ tbl, geom }) => `select ST_EstimatedExtent('${tbl[0]}','${tbl[1]}', '${geom}')`);
|
|
5
5
|
// const sqlBounds = dataset.map(el => ({ srid: el.srid || 4326, tbl: el.table.split('.'), geom: el.gcol || 'geom' })).map(({ tbl, geom, srid }) => `select st_transform(st_setsrid(ST_EstimatedExtent('${tbl[0]}','${tbl[1]}', '${geom}'),${srid}),4326) as st_estimatedextent`);
|
|
6
6
|
|
|
7
7
|
const geom = await pg.query(
|