@opengis/gis 0.2.174 → 0.2.176
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/README.md +181 -181
- package/dist/{CardIcon-Cg8bUM4G.js → CardIcon-c1Q2M3wQ.js} +1 -1
- package/dist/{EntityTablePage-DHQGlLRk.js → EntityTablePage-B5gpkH4S.js} +2 -2
- package/dist/{ExtentOutlineLayer.vue_vue_type_script_setup_true_lang-BIL1ocLY.js → ExtentOutlineLayer.vue_vue_type_script_setup_true_lang-DnGSvmMU.js} +1 -1
- package/dist/{HeaderActions.vue_vue_type_script_setup_true_lang-Buff9gTO.js → HeaderActions.vue_vue_type_script_setup_true_lang-C2B61JP6.js} +1 -1
- package/dist/{MapSettings-De-z7r5t.js → MapSettings-6-Xru5Q8.js} +2 -2
- package/dist/{RastersTablePage-Cdgkri9K.js → RastersTablePage-CXNOcTy-.js} +2 -2
- package/dist/{cartocss-BT50SVhy.js → cartocss-BFVqWvfP.js} +6 -6
- package/dist/{import-utils-CMWiuRPp.js → import-utils-uxk4LdUJ.js} +1 -1
- package/dist/{index-CHFLgCGW.js → index-CahlV-Xs.js} +39 -39
- package/dist/index.css +1 -1
- package/dist/index.js +1 -1
- package/dist/index.umd.cjs +19 -19
- package/dist/{raster-dSEb1SIG.js → raster-CEi56f0P.js} +3 -3
- package/dist/{register-D7Puupgo.js → register-NFAGBNa3.js} +3 -3
- package/dist/{service-Cz6T0O3Z.js → service-BYspag8U.js} +829 -832
- package/dist/{vs-datatable-DqVncrSg.js → vs-datatable-CHcBr6zy.js} +2 -2
- package/module/gis/cls/service_type.json +42 -42
- package/module/gis/form/gis.registers.form.json +4 -1
- package/module/test/map/addr.json +302 -302
- package/module/test/map/addr1.yml +23 -23
- package/module/test/select/address.str_id.sql +3 -3
- package/package.json +77 -77
- package/plugin.js +64 -64
- package/server/migrations/array_intersect.sql +13 -13
- package/server/migrations/cartocss.sql +27 -27
- package/server/migrations/group_list.sql +74 -74
- package/server/migrations/maps.sql +30 -30
- package/server/migrations/metadata.sql +415 -415
- package/server/migrations/ogc.sql +106 -106
- package/server/migrations/rasters.sql +265 -265
- package/server/migrations/services.sql +247 -247
- package/server/migrations/services_users_rel.sql +22 -22
- package/server/migrations/widgets.sql +20 -20
- package/server/plugins/crons.js +21 -21
- package/server/plugins/mapnik/funcs/checkRasterFile.js +107 -107
- package/server/plugins/mapnik/funcs/mapnik.js +151 -151
- package/server/plugins/mapnik/funcs/rootFolder.mjs +8 -8
- package/server/plugins/mapnik/map.proto +437 -437
- package/server/plugins/vite.js +74 -74
- package/server/routes/cartocss/cartocss.route.js +31 -31
- package/server/routes/cartocss/cartocss.route.test.js +78 -78
- package/server/routes/cartocss/cartocss.schema.js +73 -73
- package/server/routes/cartocss/functions/add.cartocss.js +39 -39
- package/server/routes/cartocss/functions/get.cartocss.js +57 -57
- package/server/routes/gis/functions/checkXML.js +107 -107
- package/server/routes/gis/functions/dashboard.js +24 -24
- package/server/routes/gis/gis.route.js +26 -26
- package/server/routes/gis/gis.route.test.js +33 -33
- package/server/routes/map/functions/clearTiles.js +35 -35
- package/server/routes/map/functions/geojson.js +187 -187
- package/server/routes/map/functions/gis.convert.js +65 -65
- package/server/routes/map/functions/gis.icon.js +220 -220
- package/server/routes/map/functions/jsonData.js +206 -206
- package/server/routes/map/functions/layerList.js +102 -102
- package/server/routes/map/functions/map.js +122 -122
- package/server/routes/map/functions/mapCatalog.js +72 -72
- package/server/routes/map/functions/mapCatalogAttribute.js +53 -53
- package/server/routes/map/functions/mapFeatures.js +133 -133
- package/server/routes/map/functions/mapFormat.js +232 -231
- package/server/routes/map/functions/mapTiles.js +162 -162
- package/server/routes/map/functions/maps.js +15 -15
- package/server/routes/map/functions/marker_icon.js +43 -43
- package/server/routes/map/functions/parseGeoFile.js +85 -85
- package/server/routes/map/functions/pointHeight.js +51 -51
- package/server/routes/map/functions/vtile.js +174 -174
- package/server/routes/map/functions/vtile1.js +185 -185
- package/server/routes/map/map.route.js +280 -280
- package/server/routes/map/map.route.test.js +503 -503
- package/server/routes/map/map.schema.js +356 -356
- package/server/routes/map/pin/circle1.svg +76 -76
- package/server/routes/map/pin/circle2.svg +18 -18
- package/server/routes/map/pin/none.svg +12 -12
- package/server/routes/map/pin/pin.svg +14 -14
- package/server/routes/map/pin/pin1.svg +16 -16
- package/server/routes/map/pin/pin2.svg +7 -7
- package/server/routes/map/pin/pin3.svg +34 -34
- package/server/routes/map/pin/pin4.svg +14 -14
- package/server/routes/map/pin/pin5.svg +7 -7
- package/server/routes/map/pin/pin6.svg +12 -12
- package/server/routes/map/pin/pin7.svg +12 -12
- package/server/routes/mapnik/functions/cartoBounds.js +32 -32
- package/server/routes/mapnik/functions/clearTiles.js +103 -103
- package/server/routes/mapnik/functions/convertBoundToTile.js +27 -27
- package/server/routes/mapnik/functions/createXml.js +76 -76
- package/server/routes/mapnik/functions/createXmlMulti.js +72 -72
- package/server/routes/mapnik/functions/fileSearch.js +35 -35
- package/server/routes/mapnik/functions/fileStat.js +28 -28
- package/server/routes/mapnik/functions/pretile.js +138 -138
- package/server/routes/mapnik/functions/rasterInfo.js +66 -66
- package/server/routes/mapnik/functions/readDir.js +22 -22
- package/server/routes/mapnik/functions/rtile.js +164 -164
- package/server/routes/mapnik/functions/uploadRaster.js +152 -152
- package/server/routes/mapnik/functions/uploadXML.js +140 -140
- package/server/routes/mapnik/mapnik.route.js +102 -102
- package/server/routes/mapnik/mapnik.route.test.js +370 -370
- package/server/routes/mapnik/mapnik.schema.mjs +195 -195
- package/server/routes/mapnik.prefixless/functions/mapnikLogger.js +31 -31
- package/server/routes/mapnik.prefixless/functions/mapnikStat.js +21 -21
- package/server/routes/mapnik.prefixless/mapnik.prefixless.route.js +20 -20
- package/server/routes/mapnik.prefixless/mapnik.prefixless.route.test.js +91 -91
- package/server/routes/mapnik.prefixless/mapnik.prefixless.schema.mjs +27 -27
- package/server/routes/maps/functions/add.map.js +42 -42
- package/server/routes/maps/functions/del.map.js +17 -17
- package/server/routes/maps/functions/get.map.js +119 -119
- package/server/routes/maps/maps.route.js +58 -58
- package/server/routes/maps/maps.route.test.js +231 -231
- package/server/routes/maps/maps.schema.js +9 -9
- package/server/routes/metadata/functions/editMetadata.js +35 -35
- package/server/routes/metadata/functions/metadataXML.js +19 -19
- package/server/routes/metadata/metadata.route.js +26 -26
- package/server/routes/metadata/metadata.route.test.js +46 -46
- package/server/routes/metadata/metadata.schema.js +53 -53
- package/server/routes/ogc/functions/map.info.point.js +117 -117
- package/server/routes/ogc/ogc.route.js +21 -21
- package/server/routes/ogc/ogc.route.test.js +53 -53
- package/server/routes/ogc/ogc.schema.js +16 -16
- package/server/routes/permissions/functions/catalog.permissions.edit.js +20 -20
- package/server/routes/permissions/functions/catalog.permissions.js +12 -12
- package/server/routes/permissions/functions/get.permissions.js +59 -59
- package/server/routes/permissions/functions/gis.catalog.js +80 -80
- package/server/routes/permissions/permissions.route.js +35 -35
- package/server/routes/permissions/permissions.route.test.js +35 -35
- package/server/routes/permissions/permissions.schema.js +43 -43
- package/server/routes/registers/functions/add.registry.js +37 -37
- package/server/routes/registers/functions/classifiers.js +100 -100
- package/server/routes/registers/functions/columns.js +3 -3
- package/server/routes/registers/functions/content.type.js +9 -9
- package/server/routes/registers/functions/del.registry.js +14 -14
- package/server/routes/registers/functions/get.info.js +93 -93
- package/server/routes/registers/functions/gis.export.js +159 -159
- package/server/routes/registers/functions/gis.registry.js +71 -71
- package/server/routes/registers/functions/gis.registry.list.js +61 -61
- package/server/routes/registers/functions/handleRegistryRequest.js +147 -147
- package/server/routes/registers/functions/map.registry.js +87 -87
- package/server/routes/registers/registers.route.js +80 -80
- package/server/routes/registers/registers.route.test.js +144 -144
- package/server/routes/registers/registers.schema.js +246 -246
- package/server/routes/root.mjs +3 -3
- package/server/routes/services/functions/add.service.js +62 -62
- package/server/routes/services/functions/create.route.js +93 -93
- package/server/routes/services/functions/del.service.js +12 -12
- package/server/routes/services/functions/get.layer.geom.js +27 -27
- package/server/routes/services/functions/get.service.users.js +76 -76
- package/server/routes/services/functions/get.services.col.js +34 -34
- package/server/routes/services/functions/get.services.js +89 -89
- package/server/routes/services/functions/legend.auto.js +77 -77
- package/server/routes/services/services.route.js +76 -76
- package/server/routes/services/services.route.test.js +167 -167
- package/server/routes/services/services.schema.js +278 -278
- package/server/routes/widgets/functions/add.widget.js +38 -38
- package/server/routes/widgets/functions/del.widget.js +17 -17
- package/server/routes/widgets/functions/get.widget.js +41 -41
- package/server/routes/widgets/widgets.route.js +61 -61
- package/server/routes/widgets/widgets.route.test.js +144 -144
- package/server/routes/widgets/widgets.schema.js +24 -24
- package/server/shared/listFiles.js +15 -0
- package/utils.js +13 -13
|
@@ -1,187 +1,187 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
3
|
-
import { createHash } from 'node:crypto';
|
|
4
|
-
import {
|
|
5
|
-
readFile, stat, mkdir, writeFile,
|
|
6
|
-
} from 'node:fs/promises';
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
pgClients, getMeta,
|
|
10
|
-
} from '@opengis/fastify-table/utils.js';
|
|
11
|
-
|
|
12
|
-
import rootFolder from '../../../plugins/mapnik/funcs/rootFolder.mjs';
|
|
13
|
-
import { BadRequestError, NotFoundError } from '@opengis/fastify-table/errors.js';
|
|
14
|
-
|
|
15
|
-
const systemColumns = ['uid', 'cdate', 'editor_id', 'editor_date', 'created_by', 'created_at', 'updated_by', 'updated_at'];
|
|
16
|
-
|
|
17
|
-
const unittest = process.env.NODE_ENV === 'test' || process.env.VITEST;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Повертає GeoJSON для сервісу на основі налаштувань з gis.services
|
|
21
|
-
*
|
|
22
|
-
* @method GET
|
|
23
|
-
* @param {string} params.layer - service_id
|
|
24
|
-
* @param {string} query.sql - повернути SQL запит замість даних
|
|
25
|
-
* @param {boolean} query.nocache - не використовувати кеш
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
export default async function geojson({
|
|
29
|
-
layer, sql, nocache, srid, type, extent,
|
|
30
|
-
}, pg = pgClients.client) {
|
|
31
|
-
if (!layer) {
|
|
32
|
-
throw BadRequestError('not enough params: layer (service_id)');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const serviceId = layer.replace('.json', '');
|
|
36
|
-
|
|
37
|
-
if (!pg.pk?.['gis.services']) {
|
|
38
|
-
throw NotFoundError('services table not found');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const service = await pg.query(
|
|
42
|
-
`select
|
|
43
|
-
source_path,
|
|
44
|
-
query,
|
|
45
|
-
geometry_column,
|
|
46
|
-
attributes,
|
|
47
|
-
srid,
|
|
48
|
-
is_public,
|
|
49
|
-
is_active,
|
|
50
|
-
geom_type
|
|
51
|
-
from gis.services
|
|
52
|
-
where service_id = $1 and is_active = true and is_public = true`,
|
|
53
|
-
[serviceId],
|
|
54
|
-
).then(r => r.rows?.[0]);
|
|
55
|
-
|
|
56
|
-
if (!service) {
|
|
57
|
-
throw NotFoundError('service not found or not available');
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const {
|
|
61
|
-
source_path: source,
|
|
62
|
-
query: serviceQuery,
|
|
63
|
-
geometry_column: gc,
|
|
64
|
-
attributes,
|
|
65
|
-
srid: serviceSrid,
|
|
66
|
-
geom_type: geomType,
|
|
67
|
-
} = service;
|
|
68
|
-
|
|
69
|
-
if (!source) {
|
|
70
|
-
throw BadRequestError('invalid service source path');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (!pg.pk?.[source]?.length) {
|
|
74
|
-
throw NotFoundError('layer table not found');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const { columns = [], geom: metaGeom } = await getMeta({ pg, table: source }) || {};
|
|
78
|
-
|
|
79
|
-
const geom = gc || metaGeom || 'geom';
|
|
80
|
-
const pk = pg.pk?.[source] || columns[0]?.name;
|
|
81
|
-
|
|
82
|
-
let attributeColumns = [];
|
|
83
|
-
if (attributes && Array.isArray(attributes)) {
|
|
84
|
-
attributeColumns = attributes
|
|
85
|
-
.filter(attr => attr.name && !systemColumns.includes(attr.name))
|
|
86
|
-
.map(attr => attr.name);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const columnsFiltered = attributeColumns.length > 0
|
|
90
|
-
? columns.filter(col => attributeColumns.includes(col.name) && !systemColumns.includes(col.name))
|
|
91
|
-
: [];
|
|
92
|
-
|
|
93
|
-
const props = columnsFiltered.map(el =>
|
|
94
|
-
`"${el.name}"${['int4'].includes(el.type) ? '::text' : ''}`
|
|
95
|
-
).join(', ');
|
|
96
|
-
|
|
97
|
-
const geometryType = (geomType || 'point').toLowerCase();
|
|
98
|
-
const limit = geometryType === 'point' ? 40000 : 2500;
|
|
99
|
-
const cacheHash = createHash('md5')
|
|
100
|
-
.update([serviceId, serviceQuery, srid, type, extent].filter(el => el).join('-'))
|
|
101
|
-
.digest('hex');
|
|
102
|
-
|
|
103
|
-
const today = new Date().toISOString().split('T')[0];
|
|
104
|
-
const filepath = path.join(
|
|
105
|
-
rootFolder,
|
|
106
|
-
`/geojson/${serviceId}/${today}/${cacheHash}.json`,
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
if (existsSync(filepath) && !nocache && !sql && !unittest) {
|
|
110
|
-
const { birthtimeMs = Date.now() } = await stat(filepath);
|
|
111
|
-
const ageInMs = Date.now() - birthtimeMs;
|
|
112
|
-
if (ageInMs < 86400000) {
|
|
113
|
-
const content = await readFile(filepath, 'utf8');
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
headers: {
|
|
117
|
-
'Content-Type': 'application/json',
|
|
118
|
-
'Cache-Control': 'public, max-age=86400',
|
|
119
|
-
},
|
|
120
|
-
data: content
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const targetSrid = srid || serviceSrid || 4326;
|
|
126
|
-
const useCentroid = type === 'centroid';
|
|
127
|
-
|
|
128
|
-
let geomExpression = geom;
|
|
129
|
-
|
|
130
|
-
if (useCentroid) {
|
|
131
|
-
geomExpression = `st_centroid(${geomExpression})`;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (targetSrid !== 4326) {
|
|
135
|
-
geomExpression = `st_transform(${geomExpression}, 4326)`;
|
|
136
|
-
} else if (serviceSrid && serviceSrid !== 4326) {
|
|
137
|
-
geomExpression = `st_transform(${geomExpression}, 4326)`;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const quotedTable = source.split('.').map(el => `"${el}"`).join('.');
|
|
141
|
-
const quotedGeom = `"${geom}"`;
|
|
142
|
-
|
|
143
|
-
const geomFinal = `st_force2d(${geomExpression.replace(new RegExp(`(\\b)${geom}(\\b)`, 'g'), `$1${quotedGeom}$2`)})`;
|
|
144
|
-
const q = `
|
|
145
|
-
SELECT 'FeatureCollection' As type,
|
|
146
|
-
json_agg(f) As features
|
|
147
|
-
FROM (
|
|
148
|
-
SELECT 'Feature' As type,
|
|
149
|
-
row_number() over() as id,
|
|
150
|
-
st_asgeojson(${geomFinal}, 6, 0)::json as geometry,
|
|
151
|
-
(select row_to_json(tc) from (
|
|
152
|
-
select "${pk}" as id ${props ? ', ' + props : ''}
|
|
153
|
-
) tc) as properties
|
|
154
|
-
FROM ${quotedTable}
|
|
155
|
-
WHERE ${serviceQuery || '1=1'}
|
|
156
|
-
AND ${quotedGeom} is not null
|
|
157
|
-
AND st_srid(${quotedGeom}) > 0
|
|
158
|
-
${extent ? `AND ${quotedGeom} && 'BOX(${extent.split(',').reduce((p, el, i) => p + el + (i % 2 ? ',' : ' '), '')})'::box2d` : ''}
|
|
159
|
-
LIMIT ${limit}
|
|
160
|
-
) f
|
|
161
|
-
`;
|
|
162
|
-
|
|
163
|
-
if (sql === 1) return q;
|
|
164
|
-
if (sql === 2) return filepath.replace(/\\/g, '/');
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const geojsonData = await pg.query(q)
|
|
168
|
-
.then(r => r.rows?.[0] || { type: 'FeatureCollection', features: [] });
|
|
169
|
-
|
|
170
|
-
geojsonData.service_id = serviceId;
|
|
171
|
-
geojsonData.count = geojsonData.features?.length || 0;
|
|
172
|
-
|
|
173
|
-
const jsonString = JSON.stringify(geojsonData);
|
|
174
|
-
|
|
175
|
-
if (!nocache && !unittest) {
|
|
176
|
-
await mkdir(path.dirname(filepath), { recursive: true });
|
|
177
|
-
await writeFile(filepath, jsonString, 'utf8');
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return {
|
|
181
|
-
headers: {
|
|
182
|
-
'Content-Type': 'application/json',
|
|
183
|
-
'Cache-Control': nocache ? 'no-cache' : 'public, max-age=86400',
|
|
184
|
-
},
|
|
185
|
-
data: jsonString
|
|
186
|
-
}
|
|
187
|
-
}
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import {
|
|
5
|
+
readFile, stat, mkdir, writeFile,
|
|
6
|
+
} from 'node:fs/promises';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
pgClients, getMeta,
|
|
10
|
+
} from '@opengis/fastify-table/utils.js';
|
|
11
|
+
|
|
12
|
+
import rootFolder from '../../../plugins/mapnik/funcs/rootFolder.mjs';
|
|
13
|
+
import { BadRequestError, NotFoundError } from '@opengis/fastify-table/errors.js';
|
|
14
|
+
|
|
15
|
+
const systemColumns = ['uid', 'cdate', 'editor_id', 'editor_date', 'created_by', 'created_at', 'updated_by', 'updated_at'];
|
|
16
|
+
|
|
17
|
+
const unittest = process.env.NODE_ENV === 'test' || process.env.VITEST;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Повертає GeoJSON для сервісу на основі налаштувань з gis.services
|
|
21
|
+
*
|
|
22
|
+
* @method GET
|
|
23
|
+
* @param {string} params.layer - service_id
|
|
24
|
+
* @param {string} query.sql - повернути SQL запит замість даних
|
|
25
|
+
* @param {boolean} query.nocache - не використовувати кеш
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export default async function geojson({
|
|
29
|
+
layer, sql, nocache, srid, type, extent,
|
|
30
|
+
}, pg = pgClients.client) {
|
|
31
|
+
if (!layer) {
|
|
32
|
+
throw BadRequestError('not enough params: layer (service_id)');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const serviceId = layer.replace('.json', '');
|
|
36
|
+
|
|
37
|
+
if (!pg.pk?.['gis.services']) {
|
|
38
|
+
throw NotFoundError('services table not found');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const service = await pg.query(
|
|
42
|
+
`select
|
|
43
|
+
source_path,
|
|
44
|
+
query,
|
|
45
|
+
geometry_column,
|
|
46
|
+
attributes,
|
|
47
|
+
srid,
|
|
48
|
+
is_public,
|
|
49
|
+
is_active,
|
|
50
|
+
geom_type
|
|
51
|
+
from gis.services
|
|
52
|
+
where service_id = $1 and is_active = true and is_public = true`,
|
|
53
|
+
[serviceId],
|
|
54
|
+
).then(r => r.rows?.[0]);
|
|
55
|
+
|
|
56
|
+
if (!service) {
|
|
57
|
+
throw NotFoundError('service not found or not available');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const {
|
|
61
|
+
source_path: source,
|
|
62
|
+
query: serviceQuery,
|
|
63
|
+
geometry_column: gc,
|
|
64
|
+
attributes,
|
|
65
|
+
srid: serviceSrid,
|
|
66
|
+
geom_type: geomType,
|
|
67
|
+
} = service;
|
|
68
|
+
|
|
69
|
+
if (!source) {
|
|
70
|
+
throw BadRequestError('invalid service source path');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!pg.pk?.[source]?.length) {
|
|
74
|
+
throw NotFoundError('layer table not found');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const { columns = [], geom: metaGeom } = await getMeta({ pg, table: source }) || {};
|
|
78
|
+
|
|
79
|
+
const geom = gc || metaGeom || 'geom';
|
|
80
|
+
const pk = pg.pk?.[source] || columns[0]?.name;
|
|
81
|
+
|
|
82
|
+
let attributeColumns = [];
|
|
83
|
+
if (attributes && Array.isArray(attributes)) {
|
|
84
|
+
attributeColumns = attributes
|
|
85
|
+
.filter(attr => attr.name && !systemColumns.includes(attr.name))
|
|
86
|
+
.map(attr => attr.name);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const columnsFiltered = attributeColumns.length > 0
|
|
90
|
+
? columns.filter(col => attributeColumns.includes(col.name) && !systemColumns.includes(col.name))
|
|
91
|
+
: [];
|
|
92
|
+
|
|
93
|
+
const props = columnsFiltered.map(el =>
|
|
94
|
+
`"${el.name}"${['int4'].includes(el.type) ? '::text' : ''}`
|
|
95
|
+
).join(', ');
|
|
96
|
+
|
|
97
|
+
const geometryType = (geomType || 'point').toLowerCase();
|
|
98
|
+
const limit = geometryType === 'point' ? 40000 : 2500;
|
|
99
|
+
const cacheHash = createHash('md5')
|
|
100
|
+
.update([serviceId, serviceQuery, srid, type, extent].filter(el => el).join('-'))
|
|
101
|
+
.digest('hex');
|
|
102
|
+
|
|
103
|
+
const today = new Date().toISOString().split('T')[0];
|
|
104
|
+
const filepath = path.join(
|
|
105
|
+
rootFolder,
|
|
106
|
+
`/geojson/${serviceId}/${today}/${cacheHash}.json`,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (existsSync(filepath) && !nocache && !sql && !unittest) {
|
|
110
|
+
const { birthtimeMs = Date.now() } = await stat(filepath);
|
|
111
|
+
const ageInMs = Date.now() - birthtimeMs;
|
|
112
|
+
if (ageInMs < 86400000) {
|
|
113
|
+
const content = await readFile(filepath, 'utf8');
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
headers: {
|
|
117
|
+
'Content-Type': 'application/json',
|
|
118
|
+
'Cache-Control': 'public, max-age=86400',
|
|
119
|
+
},
|
|
120
|
+
data: content
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const targetSrid = srid || serviceSrid || 4326;
|
|
126
|
+
const useCentroid = type === 'centroid';
|
|
127
|
+
|
|
128
|
+
let geomExpression = geom;
|
|
129
|
+
|
|
130
|
+
if (useCentroid) {
|
|
131
|
+
geomExpression = `st_centroid(${geomExpression})`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (targetSrid !== 4326) {
|
|
135
|
+
geomExpression = `st_transform(${geomExpression}, 4326)`;
|
|
136
|
+
} else if (serviceSrid && serviceSrid !== 4326) {
|
|
137
|
+
geomExpression = `st_transform(${geomExpression}, 4326)`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const quotedTable = source.split('.').map(el => `"${el}"`).join('.');
|
|
141
|
+
const quotedGeom = `"${geom}"`;
|
|
142
|
+
|
|
143
|
+
const geomFinal = `st_force2d(${geomExpression.replace(new RegExp(`(\\b)${geom}(\\b)`, 'g'), `$1${quotedGeom}$2`)})`;
|
|
144
|
+
const q = `
|
|
145
|
+
SELECT 'FeatureCollection' As type,
|
|
146
|
+
json_agg(f) As features
|
|
147
|
+
FROM (
|
|
148
|
+
SELECT 'Feature' As type,
|
|
149
|
+
row_number() over() as id,
|
|
150
|
+
st_asgeojson(${geomFinal}, 6, 0)::json as geometry,
|
|
151
|
+
(select row_to_json(tc) from (
|
|
152
|
+
select "${pk}" as id ${props ? ', ' + props : ''}
|
|
153
|
+
) tc) as properties
|
|
154
|
+
FROM ${quotedTable}
|
|
155
|
+
WHERE ${serviceQuery || '1=1'}
|
|
156
|
+
AND ${quotedGeom} is not null
|
|
157
|
+
AND st_srid(${quotedGeom}) > 0
|
|
158
|
+
${extent ? `AND ${quotedGeom} && 'BOX(${extent.split(',').reduce((p, el, i) => p + el + (i % 2 ? ',' : ' '), '')})'::box2d` : ''}
|
|
159
|
+
LIMIT ${limit}
|
|
160
|
+
) f
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
if (sql === 1) return q;
|
|
164
|
+
if (sql === 2) return filepath.replace(/\\/g, '/');
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
const geojsonData = await pg.query(q)
|
|
168
|
+
.then(r => r.rows?.[0] || { type: 'FeatureCollection', features: [] });
|
|
169
|
+
|
|
170
|
+
geojsonData.service_id = serviceId;
|
|
171
|
+
geojsonData.count = geojsonData.features?.length || 0;
|
|
172
|
+
|
|
173
|
+
const jsonString = JSON.stringify(geojsonData);
|
|
174
|
+
|
|
175
|
+
if (!nocache && !unittest) {
|
|
176
|
+
await mkdir(path.dirname(filepath), { recursive: true });
|
|
177
|
+
await writeFile(filepath, jsonString, 'utf8');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
headers: {
|
|
182
|
+
'Content-Type': 'application/json',
|
|
183
|
+
'Cache-Control': nocache ? 'no-cache' : 'public, max-age=86400',
|
|
184
|
+
},
|
|
185
|
+
data: jsonString
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
|
|
3
|
-
import { downloadFile } from "@opengis/fastify-table/utils.js";
|
|
4
|
-
import { BadRequestError, ConflictError, NotFoundError } from "@opengis/fastify-table/errors.js";
|
|
5
|
-
|
|
6
|
-
import mapnik from "../../../plugins/mapnik/funcs/mapnik.js";
|
|
7
|
-
|
|
8
|
-
const { ConvertVector } = mapnik();
|
|
9
|
-
|
|
10
|
-
const ALLOWED_FORMAT = new Set(["zip", "shp", "kml", "gml", "geojson", "json"]);
|
|
11
|
-
|
|
12
|
-
function resolveExtension(filename) {
|
|
13
|
-
return path.extname(filename || "").replace(".", "").toLowerCase();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Конвертує векторний файл у GeoJSON через mapnik ConvertVector.
|
|
18
|
-
*
|
|
19
|
-
* @method POST /gis-convert
|
|
20
|
-
* @body {string} [file] - base64 вміст файлу
|
|
21
|
-
* @body {string} [filename] - ім'я файлу (обов'язкове з file)
|
|
22
|
-
* @body {string} [filepath] - шлях до файлу на сервері (legacy)
|
|
23
|
-
* @body {number} [srid=4326] - цільовий EPSG код
|
|
24
|
-
*/
|
|
25
|
-
export default async function gisConvert({ file, filename, filepath, encoding }) {
|
|
26
|
-
let data;
|
|
27
|
-
let sourceName;
|
|
28
|
-
|
|
29
|
-
if (typeof file === "string" && file) {
|
|
30
|
-
if (!filename || typeof filename !== "string") {
|
|
31
|
-
throw BadRequestError("Потрібен параметр filename разом з file");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
data = Buffer.from(file, "base64");
|
|
35
|
-
sourceName = path.basename(filename);
|
|
36
|
-
} else if (typeof filepath === "string" && filepath) {
|
|
37
|
-
const filepath1 = filepath.replace(/%2e/g, ".").replace(/%2E/g, ".");
|
|
38
|
-
if (filepath1.includes('..')) {
|
|
39
|
-
throw ConflictError('path traversals are not allowed');
|
|
40
|
-
}
|
|
41
|
-
data = await downloadFile(filepath, { buffer: true });
|
|
42
|
-
if (!data) {
|
|
43
|
-
throw NotFoundError(`Файл не знайдено: ${filepath}`);
|
|
44
|
-
}
|
|
45
|
-
sourceName = path.basename(filepath);
|
|
46
|
-
} else {
|
|
47
|
-
throw BadRequestError("Потрібен параметр file (base64) або filepath");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const ext = resolveExtension(sourceName);
|
|
51
|
-
|
|
52
|
-
if (!ALLOWED_FORMAT.has(ext)) {
|
|
53
|
-
throw BadRequestError(`Непідтримуване розширення: .${ext}. Підтримується: ${[...ALLOWED_FORMAT].join(", ")}`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const res = await ConvertVector({
|
|
57
|
-
data,
|
|
58
|
-
format: ext,
|
|
59
|
-
// srs: +srid, // target srs, 4326 by default
|
|
60
|
-
encoding,
|
|
61
|
-
filename: sourceName,
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
return JSON.parse(res.geojson);
|
|
65
|
-
}
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { downloadFile } from "@opengis/fastify-table/utils.js";
|
|
4
|
+
import { BadRequestError, ConflictError, NotFoundError } from "@opengis/fastify-table/errors.js";
|
|
5
|
+
|
|
6
|
+
import mapnik from "../../../plugins/mapnik/funcs/mapnik.js";
|
|
7
|
+
|
|
8
|
+
const { ConvertVector } = mapnik();
|
|
9
|
+
|
|
10
|
+
const ALLOWED_FORMAT = new Set(["zip", "shp", "kml", "gml", "geojson", "json"]);
|
|
11
|
+
|
|
12
|
+
function resolveExtension(filename) {
|
|
13
|
+
return path.extname(filename || "").replace(".", "").toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Конвертує векторний файл у GeoJSON через mapnik ConvertVector.
|
|
18
|
+
*
|
|
19
|
+
* @method POST /gis-convert
|
|
20
|
+
* @body {string} [file] - base64 вміст файлу
|
|
21
|
+
* @body {string} [filename] - ім'я файлу (обов'язкове з file)
|
|
22
|
+
* @body {string} [filepath] - шлях до файлу на сервері (legacy)
|
|
23
|
+
* @body {number} [srid=4326] - цільовий EPSG код
|
|
24
|
+
*/
|
|
25
|
+
export default async function gisConvert({ file, filename, filepath, encoding }) {
|
|
26
|
+
let data;
|
|
27
|
+
let sourceName;
|
|
28
|
+
|
|
29
|
+
if (typeof file === "string" && file) {
|
|
30
|
+
if (!filename || typeof filename !== "string") {
|
|
31
|
+
throw BadRequestError("Потрібен параметр filename разом з file");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
data = Buffer.from(file, "base64");
|
|
35
|
+
sourceName = path.basename(filename);
|
|
36
|
+
} else if (typeof filepath === "string" && filepath) {
|
|
37
|
+
const filepath1 = filepath.replace(/%2e/g, ".").replace(/%2E/g, ".");
|
|
38
|
+
if (filepath1.includes('..')) {
|
|
39
|
+
throw ConflictError('path traversals are not allowed');
|
|
40
|
+
}
|
|
41
|
+
data = await downloadFile(filepath, { buffer: true });
|
|
42
|
+
if (!data) {
|
|
43
|
+
throw NotFoundError(`Файл не знайдено: ${filepath}`);
|
|
44
|
+
}
|
|
45
|
+
sourceName = path.basename(filepath);
|
|
46
|
+
} else {
|
|
47
|
+
throw BadRequestError("Потрібен параметр file (base64) або filepath");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const ext = resolveExtension(sourceName);
|
|
51
|
+
|
|
52
|
+
if (!ALLOWED_FORMAT.has(ext)) {
|
|
53
|
+
throw BadRequestError(`Непідтримуване розширення: .${ext}. Підтримується: ${[...ALLOWED_FORMAT].join(", ")}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const res = await ConvertVector({
|
|
57
|
+
data,
|
|
58
|
+
format: ext,
|
|
59
|
+
// srs: +srid, // target srs, 4326 by default
|
|
60
|
+
encoding,
|
|
61
|
+
filename: sourceName,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return JSON.parse(res.geojson);
|
|
65
|
+
}
|