@opengis/gis 0.2.1 → 0.2.3

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.
@@ -0,0 +1,33 @@
1
+ source_path: data_bp_myo.bp
2
+ name: Будівельні паспорти Template
3
+ visible: true
4
+ style:
5
+ type: point
6
+ color: '#d970d5'
7
+ width: 2
8
+ radius: 10
9
+ border: 1
10
+ opacity: 0.4
11
+ popup:
12
+ - ua: Назва замовника
13
+ name: bp_customer_name
14
+ meta: title
15
+ - label: Реєстраційний номер БП
16
+ name: bp_code
17
+ format: badge
18
+ - label: Адреса
19
+ name: address
20
+ card:
21
+ - label: Реєстраційний номер БП
22
+ name: bp_code
23
+ - ua: Адреса
24
+ name: address
25
+ - ua: Вид будівництва
26
+ data: bp_build_type
27
+ name: bp_build_type
28
+ - ua: Назва замовника
29
+ name: bp_customer_name
30
+ - ua: Статус документа
31
+ data: doc_status
32
+ name: bp_doc_status
33
+
@@ -0,0 +1,54 @@
1
+ # Title: Parcel Polygon
2
+
3
+ name: ФОП
4
+ source_path: economy_profile.individual
5
+ query: 1=1
6
+
7
+ style:
8
+ type: point
9
+ color: orange
10
+ width: 1
11
+ border: 1
12
+ opacity: 0.6
13
+ stroke: "#eee"
14
+
15
+ popup:
16
+ - name: registration_date
17
+ meta: title
18
+ - ua: Назва замовника
19
+ name: bp_customer_name
20
+ meta: title
21
+ - label: Реєстраційний номер БП
22
+ name: bp_code
23
+ format: badge
24
+ - label: Адреса
25
+ name: address
26
+
27
+ card:
28
+ - label: Адреса
29
+ name: address
30
+
31
+
32
+ filters:
33
+ - name: registration_date
34
+ label: Дата реєстрації
35
+ type: date
36
+ - name: fop_status
37
+ label: Статус
38
+ type: Autocomplete
39
+ data: fop_status
40
+
41
+ - name: address_id
42
+ label: Адреса
43
+ type: Autocomplete
44
+ data: reg_address_id
45
+ - name: kved_main
46
+ label: КВЕД (основний)
47
+ type: Autocomplete
48
+ data: kved_main
49
+ - name: before_local_budget
50
+ label: Податковий борг перед місцевим бюджетом
51
+ type: Range
52
+ - name: before_state_budget
53
+ label: Податковий борг перед державним бюджетом
54
+ type: Range
@@ -0,0 +1,21 @@
1
+ history: true
2
+ name: Адресний реєстр
3
+ height: calc(100vh - 65px)
4
+ center:
5
+ - 34.65450
6
+ - 48.51174
7
+ zoom: 11
8
+ layers:
9
+ - faafbea5263a
10
+ widgets:
11
+ - type: search
12
+ position: top-left
13
+ - type: info
14
+ position: top-left
15
+ config:
16
+ title: Адресний реєстр
17
+ content: "<p style='font-size: 14px; color: #34495E;'>Карта адресного реєстру відображає офіційно зареєстровані адреси на території громади або населеного пункту. До складу інформації входять дані про вулиці, будинки, типи об'єктів (житлові, комерційні, адміністративні тощо), а також структуровані компоненти адреси: назва вулиці, номер будинку, корпус, літера, тощо.</p>"
18
+ - type: layers
19
+ position: top-left
20
+ - type: basemaps
21
+ position: bottom-left
@@ -7,7 +7,11 @@
7
7
  48.51174
8
8
  ],
9
9
  "zoom": 11,
10
- "widgets":[
10
+ "layers": [
11
+ "3c95e341465d",
12
+ "dbcb186818e3"
13
+ ],
14
+ "widgets": [
11
15
  {
12
16
  "type": "search",
13
17
  "position": "top-left"
@@ -15,14 +19,18 @@
15
19
  {
16
20
  "type": "info",
17
21
  "position": "top-left",
18
- "config":{
19
- "title": "Містобудівна документація",
20
- "content": "<p style='font-size: 14px; color: #34495E;'>Затверджені текстові і графічні матеріали, якими регулюється планування, забудова та інше використання територій.</p>"
22
+ "config": {
23
+ "title": "Містобудівна документація",
24
+ "content": "<p style='font-size: 14px; color: #34495E;'>Затверджені текстові і графічні матеріали, якими регулюється планування, забудова та інше використання територій.</p>"
21
25
  }
22
26
  },
23
- {
27
+ {
24
28
  "type": "layers",
25
- "position": "top-left"
29
+ "position": "top-left",
30
+ "config": {
31
+ "title": "Шари мапи",
32
+ "description": "Доступні шари мапи містобудівна документація"
33
+ }
26
34
  },
27
35
  {
28
36
  "type": "basemaps",
@@ -32,10 +40,5 @@
32
40
  "type": "legend",
33
41
  "position": "bottom-right"
34
42
  }
35
- ],
36
- "old":["bp","3c95e341465d"],
37
- "layers": [
38
- "3c95e341465d",
39
- "dbcb186818e3"
40
43
  ]
41
- }
44
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "history": true,
3
+ "height": "calc(100vh - 65px)",
4
+ "center": [
5
+ 34.65450,
6
+ 48.51174
7
+ ],
8
+ "layers":["bp1"],
9
+ "widgets": [
10
+ {
11
+ "type": "info",
12
+ "position": "top-left",
13
+ "config": {
14
+ "title": "Будівельні паспорти",
15
+ "content": "<p style='font-size: 14px; color: #34495E;'>Затверджені текстові і графічні матеріали, якими регулюється планування, забудова та інше використання територій.</p>"
16
+ }
17
+ },
18
+ {
19
+ "type": "layers",
20
+ "position": "top-left",
21
+ "visible": true
22
+ },
23
+ {
24
+ "type": "dataset",
25
+ "position": "top-right",
26
+ "title": "Густота населення",
27
+ "config": {
28
+ "layer": "bp",
29
+ "attribute": "status"
30
+ },
31
+ "visible": true
32
+ },
33
+
34
+
35
+ {
36
+ "type": "basemaps",
37
+ "position": "bottom-left",
38
+ "title": "Базові карти",
39
+ "visible": true
40
+ }
41
+
42
+ ]
43
+
44
+ }
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "history": true,
3
- "height": "calc(100vh - 65px)",
4
- "center": [
3
+ "height": "calc(100vh - 65px)",
4
+ "center": [
5
5
  34.65450,
6
- 48.51174
6
+ 48.51174
7
7
  ],
8
8
  "layers":["9587e69d35fd"],
9
9
  "widgets": [
@@ -13,7 +13,7 @@
13
13
  "config": {
14
14
  "title": "Містобудівна документація",
15
15
  "content": "<p style='font-size: 14px; color: #34495E;'>Затверджені текстові і графічні матеріали, якими регулюється планування, забудова та інше використання територій.</p>"
16
- }
16
+ }
17
17
  },
18
18
  {
19
19
  "type": "layers",
@@ -22,16 +22,16 @@
22
22
  },
23
23
  {
24
24
  "type": "dataset",
25
- "position": "top-right",
25
+ "position": "top-left",
26
26
  "title": "Густота населення",
27
27
  "config": {
28
- "layer_id": "municipalities",
29
- "attribute": "status"
28
+ "layer": "9587e69d35fd",
29
+ "list":[{"id":"status","text":"Статус" },{"id":"type_work_id","text":"Тип робіт"}]
30
30
  },
31
31
  "visible": true
32
32
  },
33
-
34
-
33
+
34
+
35
35
  {
36
36
  "type": "legend",
37
37
  "position": "bottom-right",
@@ -75,12 +75,12 @@
75
75
  {
76
76
  "type": "basemaps",
77
77
  "position": "bottom-left",
78
- "title": "Базові карти",
78
+ "title": "Базові карти",
79
79
  "visible": true
80
80
  }
81
-
81
+
82
82
  ],
83
-
83
+
84
84
  "tools": [
85
85
  "home",
86
86
  "geolocation",
@@ -88,4 +88,4 @@
88
88
  "area",
89
89
  "print"
90
90
  ]
91
- }
91
+ }
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "history": true,
3
- "height": "calc(100vh - 65px)",
4
- "center": [
3
+ "height": "calc(100vh - 65px)",
4
+ "center": [
5
5
  34.65450,
6
- 48.51174
6
+ 48.51174
7
7
  ],
8
8
  "zoom": 11,
9
9
  "layers": ["ed67980cd690"],
@@ -13,31 +13,35 @@
13
13
  "position": "top-left",
14
14
  "config": {
15
15
  "title": "Тимчасові споруди",
16
- "content": "<p style='font-size: 14px; color: #34495E;'>Інформація про тимчасові споруди встановлені на території м. Кам'янське</p>"
16
+ "content": "<p style='font-size: 14px; color: #34495E;'>Інформація про тимчасові споруди встановлені на території м. Кам'янське</p>"
17
17
  }
18
18
  },
19
19
  {
20
20
  "type": "dataset",
21
21
  "position": "top-left",
22
22
  "config": {
23
- "layer":"ed67980cd690",
24
- "attribute":"status"
23
+ "layer":"ed67980cd690",
24
+ "attribute":"temp_structure_type",
25
+ "colors":{
26
+ "1":"#3a7a57",
27
+ "2":"#8e4484"
28
+ }
25
29
  }
26
30
  },
27
31
  {
28
32
  "type": "layers",
29
- "visible":false
33
+ "visible":false
30
34
  },
31
35
  {
32
36
  "type": "legend",
33
- "position":"bottom-right"
37
+ "position":"bottom-right"
34
38
  },
35
39
  {
36
40
  "type": "basemaps",
37
41
  "position": "bottom-left",
38
- "title": "Базові карти"
42
+ "title": "Базові карти"
39
43
  }
40
- ],
44
+ ],
41
45
  "tools": [
42
46
  "home",
43
47
  "geolocation",
@@ -45,4 +49,4 @@
45
49
  "area",
46
50
  "print"
47
51
  ]
48
- }
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/gis",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "author": "Softpro",
6
6
  "main": "./dist/index.js",
@@ -16,6 +16,8 @@
16
16
  ],
17
17
  "scripts": {
18
18
  "start1": "bun server",
19
+ "lint": "eslint src --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts",
20
+ "fix": "eslint src --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
19
21
  "patch": "npm version patch && git push && npm publish",
20
22
  "dev": "bun --hot server",
21
23
  "front": "vite dev",
@@ -35,21 +37,25 @@
35
37
  "carto": "0.16.3"
36
38
  },
37
39
  "peerDependencies": {
38
- "@opengis/fastify-table": "^2.0.23"
40
+ "@opengis/fastify-table": "^2.0.32"
39
41
  },
40
42
  "resolutions": {
41
43
  "rollup": "4.30.0"
42
44
  },
43
45
  "devDependencies": {
44
46
  "@opengis/core": "^0.0.23",
45
- "@opengis/fastify-table": "^2.0.23",
47
+ "@opengis/fastify-table": "^2.0.32",
46
48
  "@opengis/filter": "^0.1.7",
47
- "@opengis/form": "^0.0.42",
49
+ "@opengis/form": "^0.0.48",
48
50
  "@opengis/table": "^0.0.27",
49
51
  "@vitejs/plugin-vue": "^5.2.3",
50
52
  "axios": "^1.11.0",
51
53
  "eslint": "8.49.0",
52
54
  "eslint-config-airbnb": "19.0.4",
55
+ "eslint-plugin-import": "^2.25.3",
56
+ "eslint-plugin-vue": "^9.17.0",
57
+ "@vue/eslint-config-typescript": "^12.0.0",
58
+ "vue-eslint-parser": "^10.2.0",
53
59
  "lucide-vue-next": "^0.514.0",
54
60
  "sass-embedded": "1.86.3",
55
61
  "typescript": "^5.9.2",
@@ -58,4 +64,4 @@
58
64
  "vue-router": "4.5.1",
59
65
  "vuedraggable": "^4.1.0"
60
66
  }
61
- }
67
+ }
package/plugin.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import fp from 'fastify-plugin';
2
2
 
3
- import { config, addHook, getMeta, pgClients, execMigrations } from '@opengis/fastify-table/utils.js';
3
+ import {
4
+ config, addHook, getMeta, pgClients, execMigrations,
5
+ } from '@opengis/fastify-table/utils.js';
4
6
 
5
7
  config.prefix = config.prefix || '/api';
6
8
 
@@ -38,4 +40,4 @@ async function plugin(app, opts = config) {
38
40
  Object.assign(payload, { fields: columns?.map?.(({ name, title, dataTypeID }) => ({ name, title: title || name, type: columnType[pg?.pgType?.[dataTypeID] || 'text'] })).filter(el => el.type) });
39
41
  });
40
42
  }
41
- export default fp(plugin)
43
+ export default fp(plugin);
@@ -9,9 +9,8 @@ const isProduction = process.env.NODE_ENV === 'production' || config.production;
9
9
  console.log({ isProduction });
10
10
 
11
11
  async function plugin(fastify, opts) {
12
-
13
12
  const viteServer = !isProduction ? await createServer({
14
- root: `admin`,
13
+ root: 'admin',
15
14
  server: {
16
15
  middlewareMode: true,
17
16
  },
@@ -21,7 +20,6 @@ async function plugin(fastify, opts) {
21
20
  const dir1 = process.cwd();
22
21
  viteServer.watcher.add(dir1);
23
22
  viteServer.watcher.on('all', (d, t) => {
24
-
25
23
  if (!t.includes('module')) return;
26
24
  console.log(d, t);
27
25
  viteServer.ws.send({ type: 'full-reload' });
@@ -49,7 +47,7 @@ async function plugin(fastify, opts) {
49
47
  const filePath = fs.existsSync(filePathDist) ? filePathDist : filePathAssets; // check dist or assets
50
48
  const ext = path.extname(filePath);
51
49
 
52
- if (!fs.existsSync(filePath)) return { status: 404, message: 'not found' }
50
+ if (!fs.existsSync(filePath)) return { status: 404, message: 'not found' };
53
51
  const mime = {
54
52
  '.js': 'text/javascript', '.css': 'text/css', '.woff2': 'application/font-woff', '.png': 'image/png', '.svg': 'image/svg+xml', '.jpg': 'image/jpg',
55
53
  }[ext];
@@ -69,9 +67,7 @@ async function plugin(fastify, opts) {
69
67
  if (!isProduction) return null; // admin vite
70
68
  const stream = fs.createReadStream('admin/dist/index.html');
71
69
  return reply.type('text/html').send(stream);
72
-
73
- })
70
+ });
74
71
  }
75
72
 
76
73
  export default plugin;
77
-
@@ -1,32 +1,33 @@
1
- import { metaFormat, pgClients } from "@opengis/fastify-table/utils.js";
1
+ import { metaFormat, pgClients, getTemplate } from '@opengis/fastify-table/utils.js';
2
2
 
3
3
  export default async function getServicesCol({ params = {}, pg = pgClients.client }, reply) {
4
- const row = await pg.query(`
4
+ const row = await getTemplate('layer', params.id) || await pg.query(`
5
5
  SELECT
6
6
  service_id, source_path, attributes
7
7
  FROM gis.services where ${params.id ? 'service_id=$1' : '1=1'}
8
8
  `, [params.id].filter(Boolean)).then(el => el.rows[0] || {});
9
9
 
10
- if (!row.source_path) {
11
- return reply.status(404).send({ code: 404, message: 'source not found' });
12
- }
10
+ if (!row.source_path) {
11
+ return reply.status(404).send({ code: 404, message: 'source not found' });
12
+ }
13
13
 
14
- const fields = await pg.query(` SELECT * FROM ${row.source_path} limit 0`).then(el => el.fields);
15
- const field = fields.find(el => el.name == params.col);
14
+ const fields = await pg.query(` SELECT * FROM ${row.source_path} limit 0`).then(el => el.fields);
15
+ const field = fields.find(el => el.name === params.col);
16
16
 
17
- if (!field) {
18
- return reply.status(404).send({ message: { error: 'column not found', fields }, code: 404 });
19
- }
17
+ if (!field) {
18
+ return reply.status(404).send({ message: { error: 'column not found', fields }, code: 404 });
19
+ }
20
20
 
21
- const rows = await pg.query(`SELECT ${params.col}::text as id, ${params.col}, count(*) FROM ${row.source_path}
22
- where ${params.col} is not null group by ${params.col} order by count(*) desc limit 10`
23
- ).then(el => el.rows || []);
21
+ const rows = await pg.query(`SELECT ${params.col}::text as id, ${params.col}, count(*) FROM ${row.source_path}
22
+ where ${params.col} is not null group by ${params.col} order by count(*) desc limit 10`).then(el => el.rows || []);
24
23
 
25
- const cls = row.attributes?.filter?.((col) => col.name && col.data && ["select", "badge", "tags"].includes(col.format)).reduce((acc, curr) => ({ ...acc, [curr.name]: curr.data }), {}) || {};
24
+ const cls = row.attributes?.filter?.((col) => col.name && col.data && ['select', 'badge', 'tags'].includes(col.format)).reduce((acc, curr) => ({ ...acc, [curr.name]: curr.data }), {}) || {};
26
25
 
27
- await metaFormat({ rows, table: row.source_path, cls });
26
+ await metaFormat({ rows, table: row.source_path, cls });
28
27
 
29
- const rows1 = rows.map(row => ({ ...row[`${params.col}_data`], ...row, [`${params.col}_data`]: undefined, [params.col]: undefined }));
28
+ const rows1 = rows.map(row => ({
29
+ ...row[`${params.col}_data`], ...row, [`${params.col}_data`]: undefined, [params.col]: undefined,
30
+ }));
30
31
 
31
- return reply.status(200).send({ rows: rows1, field, fields });
32
- }
32
+ return reply.status(200).send({ rows: rows1, field, fields });
33
+ }
@@ -1,17 +1,22 @@
1
- import { dataUpdate, getMeta, pgClients, yml2json } from "@opengis/fastify-table/utils.js";
1
+ import {
2
+ dataUpdate, getMeta, pgClients, yml2json, getTemplate,
3
+ } from '@opengis/fastify-table/utils.js';
2
4
 
3
5
  const columnType = {
4
- text: 'text',
5
- date: 'date',
6
- bool: 'yes/no',
7
- numeric: 'number',
8
- integer: 'number',
9
- 'timestamp without time zone': 'date',
10
- 'timestamp with time zone': 'date',
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',
11
13
  };
12
14
 
13
15
  export default async function getServices({ params = {}, pg = pgClients.client }, reply) {
14
- const rows = await pg.query(`
16
+ const t = await getTemplate('layer', params.id);
17
+ if (t) return t;
18
+ if (!pg.tlist.includes('gis.services')) return { status: 404, message: 'not found' };
19
+ const rows = await pg.query(`
15
20
  SELECT
16
21
  service_id, service_key, name, description, keywords, category, holder, group_id, b.group_name, service_type,
17
22
  source_type, service_url, source_path, query, geom_type, geometry_column, sql_list, attributes, filters,
@@ -22,58 +27,58 @@ export default async function getServices({ params = {}, pg = pgClients.client }
22
27
  where ${params.id ? 'service_id=$1' : '1=1'}
23
28
  `, [params.id].filter(Boolean)).then(el => el.rows || []);
24
29
 
25
- if (params.id && !rows.length) {
26
- return reply.status(404).send('service not found');
27
- }
30
+ if (params.id && !rows.length) {
31
+ return reply.status(404).send('service not found');
32
+ }
28
33
 
34
+ const totals = pg.queryCache ? await pg.queryCache('select json_object_agg(oid::regclass, reltuples) from pg_class')
35
+ .then(el => el.rows?.[0]?.json_object_agg || {}) : {};
29
36
 
30
- const totals = pg.queryCache ? await pg.queryCache(`select json_object_agg(oid::regclass, reltuples) from pg_class`)
31
- .then(el => el.rows?.[0]?.json_object_agg || {}) : {};
37
+ if (params.id) {
38
+ const html = await pg.query('select body from admin.templates where template_id=$1', [params.id]).then(el => el.rows?.[0]?.body);
39
+ Object.assign(rows[0], { html });
40
+ }
32
41
 
33
- if (params.id) {
34
- const html = await pg.query('select body from admin.templates where template_id=$1', [params.id]).then(el => el.rows?.[0]?.body);
35
- Object.assign(rows[0], { html });
36
- }
42
+ rows.filter(row => row.source_path).forEach(row => {
43
+ Object.assign(row, { count: totals[row.source_path] || 0 });
44
+ });
37
45
 
38
- rows.filter(row => row.source_path).forEach(row => {
39
- Object.assign(row, { count: totals[row.source_path] || 0 });
40
- });
41
-
42
- const { columns = [] } = await getMeta({ pg, table: rows[0].source_path }) || {};
43
-
44
- const fields = columns.map(({ name, dataTypeID, title }) => ({ name, type: columnType[pg.pgType?.[dataTypeID] || 'text'], label: title || name }));
45
-
46
- const noCenterIds = rows.filter(row => !row.center && row.bbox).map(row => row.service_id);
47
-
48
- const centers = noCenterIds.length ? await pg.query(`SELECT json_object_agg(service_id, st_pointonsurface(bbox)::json) FROM gis.services where service_id=any($1)`, [noCenterIds].filter(Boolean)).then(el => el.rows?.[0]?.json_object_agg || {}) : {};
49
-
50
- await Promise.all(rows.filter(row => centers[row.service_id]).map(async (row) => {
51
- // console.log('update service center', row.service_id, JSON.stringify(centers[row.service_id]));
52
- await dataUpdate({ pg, id: row.service_id, table: 'gis.services', data: { center: centers[row.service_id] } });
53
- Object.assign(row, { center: centers[row.service_id] });
54
- }));
55
-
56
- rows.forEach(row => Object.assign(row, {
57
- center: row.center?.coordinates,
58
- style: row.style ? yml2json(row.style) : undefined
59
- }));
60
- rows.filter(row => row.extent).forEach(row => Object.assign(row, {
61
- extent: row.extent.match(/BOX\(([^)]+)\)/)?.[1]
62
- ?.replace?.(/ /g, ",")
63
- ?.split?.(",")
64
- }));
46
+ const { columns = [] } = await getMeta({ pg, table: rows[0].source_path }) || {};
65
47
 
48
+ const fields = columns.map(({ name, dataTypeID, title }) => ({ name, type: columnType[pg.pgType?.[dataTypeID] || 'text'], label: title || name }));
66
49
 
67
- rows.filter(row => row.filters).forEach(row => Object.assign(row, {
68
- filters: row.filters.map(filter => ({ ...filter, api: '/api/suggest/' + row.source_path + ':' + filter.name + (filter.data && filter.name !== filter.data ? `?sel=${filter.data}` : '') }))
69
- }));
50
+ const noCenterIds = rows.filter(row => !row.center && row.bbox).map(row => row.service_id);
70
51
 
71
- if (params.id) {
72
- return reply.status(200).send({ ...rows[0], fields });
73
- }
52
+ const centers = noCenterIds.length ? await pg.query('SELECT json_object_agg(service_id, st_pointonsurface(bbox)::json) FROM gis.services where service_id=any($1)', [noCenterIds].filter(Boolean)).then(el => el.rows?.[0]?.json_object_agg || {}) : {};
74
53
 
75
- return reply.status(200).send({
76
- rows,
77
- fields,
54
+ await Promise.all(rows.filter(row => centers[row.service_id]).map(async (row) => {
55
+ // console.log('update service center', row.service_id, JSON.stringify(centers[row.service_id]));
56
+ await dataUpdate({
57
+ pg, id: row.service_id, table: 'gis.services', data: { center: centers[row.service_id] },
78
58
  });
79
- }
59
+ Object.assign(row, { center: centers[row.service_id] });
60
+ }));
61
+
62
+ rows.forEach(row => Object.assign(row, {
63
+ center: row.center?.coordinates,
64
+ style: row.style ? yml2json(row.style) : undefined,
65
+ }));
66
+ rows.filter(row => row.extent).forEach(row => Object.assign(row, {
67
+ extent: row.extent.match(/BOX\(([^)]+)\)/)?.[1]
68
+ ?.replace?.(/ /g, ',')
69
+ ?.split?.(','),
70
+ }));
71
+
72
+ rows.filter(row => row.filters).forEach(row => Object.assign(row, {
73
+ filters: row.filters.map(filter => ({ ...filter, api: `/api/suggest/${row.source_path}:${filter.name}${filter.data && filter.name !== filter.data ? `?sel=${filter.data}` : ''}` })),
74
+ }));
75
+
76
+ if (params.id) {
77
+ return reply.status(200).send({ ...rows[0], fields });
78
+ }
79
+
80
+ return reply.status(200).send({
81
+ rows,
82
+ fields,
83
+ });
84
+ }