@opengis/gis 0.1.81 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/index.css +1 -1
  2. package/dist/index.js +5752 -6487
  3. package/dist/index.umd.cjs +39 -55
  4. package/module/gis/table/gis.group_list.table.json +36 -0
  5. package/module/test/cls/ts.temp_structure.ts_class.json +50 -0
  6. package/module/test/layer/bp.json +60 -0
  7. package/module/test/map/address/addr.yml +6 -0
  8. package/module/test/map/address/index.yml +22 -0
  9. package/module/test/map/address/street.yml +16 -0
  10. package/module/test/map/address2/addr.json +9 -0
  11. package/module/test/map/address2/index.json +35 -0
  12. package/module/test/map/address2/street.json +19 -0
  13. package/module/test/map/address3.yml +52 -0
  14. package/module/test/map/address4.json +34 -0
  15. package/module/test/map/bp_myo.json +37 -0
  16. package/module/test/map/main.json +44 -0
  17. package/module/test/map/mbd.json +91 -0
  18. package/module/test/map/ts.json +20 -129
  19. package/module/test/table/data_address.street.table.json +70 -0
  20. package/package.json +13 -11
  21. package/server/migrations/services.sql +3 -0
  22. package/server/migrations/widgets.sql +21 -0
  23. package/server/plugins/mapnik/funcs/map.proto +241 -241
  24. package/server/routes/gis/index.mjs +2 -0
  25. package/server/routes/gis/registers/funcs/classifiers.js +18 -3
  26. package/server/routes/gis/registers/funcs/handleRegistryRequest.js +4 -2
  27. package/server/routes/gis/services/add.service.js +51 -16
  28. package/server/routes/gis/services/get.services.js +28 -5
  29. package/server/routes/gis/services/legend.auto.js +78 -0
  30. package/server/routes/map/controllers/mapFormat.js +19 -10
  31. package/server/routes/map/controllers/marker_icon.js +2 -1
  32. package/server/routes/map/index.mjs +36 -15
  33. package/server/routes/map/maps/add.map.js +42 -0
  34. package/server/routes/map/maps/del.map.js +19 -0
  35. package/server/routes/map/maps/get.map.js +65 -0
  36. package/server/routes/map/vtile1.js +13 -4
  37. package/server/routes/map/widgets/add.widget.js +39 -0
  38. package/server/routes/map/widgets/del.widget.js +23 -0
  39. package/server/routes/map/widgets/get.widget.js +41 -0
@@ -1,241 +1,241 @@
1
- syntax = "proto3";
2
-
3
-
4
- message render_in {
5
- string path = 1;
6
- string name = 2;
7
- repeated double bbox = 3 [packed=true];
8
- string tile = 4;
9
- string xml = 5;
10
- int32 width = 6;
11
- int32 height = 7;
12
- }
13
-
14
-
15
- message render_out {
16
- string err = 1;
17
- string base64 = 2;
18
- string tile = 3;
19
- }
20
-
21
- message xml_in {
22
- string path = 1;
23
- string name = 2;
24
- string xml = 3;
25
- bool reload = 4;
26
- }
27
-
28
- message xml_out {
29
- string err = 1;
30
- bool is_ok = 2;
31
- }
32
-
33
- message cmd_in {
34
- string cmd = 1;
35
- string svg = 2;
36
- string filename = 3;
37
- }
38
-
39
- message cmd_out {
40
- string err = 1;
41
- string base64 = 2;
42
- }
43
-
44
- message gdal_in {
45
- string name = 1;
46
- string path = 2;
47
- string out = 3;
48
- string params = 4;
49
- }
50
- message gdal_out {
51
- string result = 1;
52
- string err = 2;
53
- }
54
-
55
- message clear_tiles_in {
56
- string path = 1;
57
- }
58
-
59
- message clear_tiles_out {
60
- oneof clear_message {
61
- bool ok = 1;
62
- string err = 2;
63
- }
64
- }
65
-
66
- message empty {}
67
-
68
- message statusRenderInfo {
69
- string name = 1;
70
- bool isActive = 2;
71
- float workTime = 3;
72
- }
73
-
74
- message statusInfo {
75
- repeated statusRenderInfo info = 1;
76
- }
77
-
78
- message printMapOut {
79
- string map = 1;
80
- string err = 2;
81
- }
82
-
83
- message xyz {
84
- float x = 1;
85
- float y = 2;
86
- float z = 3;
87
- }
88
-
89
- message printMapIn {
90
- string baseurl = 1;
91
- string baseImg = 2;
92
- repeated string xml = 3;
93
- string overlayXml = 4;
94
- string overlay = 5;
95
- int32 width = 6;
96
- int32 height = 7;
97
- repeated double bbox = 8;
98
- xyz pos = 9;
99
- string geojsonSettings = 10;
100
- }
101
-
102
- message log_in {
103
- uint32 rows = 1;
104
- string level = 2;
105
- }
106
-
107
- message log_out {
108
- repeated string logs = 1;
109
- }
110
-
111
- message sqlToShpIn {
112
- string db = 1;
113
- string host = 2;
114
- string port = 3;
115
- string sql = 4;
116
- string savePath = 5;
117
- string shpName = 6;
118
- }
119
-
120
- message shapeOut {
121
- string zipPath = 1;
122
- }
123
-
124
- message fileIn {
125
- bytes fileBytes = 1;
126
- string relativeFilepath = 2;
127
- }
128
-
129
- message deleteFileIn {
130
- string dir = 1;
131
- repeated string objectList = 2;
132
- }
133
-
134
- message fileManagerOut {
135
- bool status = 1;
136
- string operation = 2;
137
- }
138
-
139
- message checkFilesDir {
140
- string dir = 1;
141
- repeated string filesSearch = 2;
142
- }
143
-
144
- enum status {
145
- LOADING = 0;
146
- OK = 1;
147
- }
148
-
149
- message filesStatus {
150
- status fileLoadingStatus = 1;
151
- }
152
-
153
- message fileListIn {
154
- string dir = 1;
155
- }
156
-
157
- message fileInfo {
158
- repeated fileInfoL fileInfoList = 1;
159
- }
160
-
161
- message fileInfoL {
162
- oneof fileInfo {
163
- fileInfoObj file = 1;
164
- dirInfoObj dir = 2;
165
- }
166
- }
167
-
168
- message fileInfoObj {
169
- string type = 1;
170
- string birthtime = 2;
171
- string name = 3;
172
- uint64 size = 4;
173
- string ext = 5;
174
- string server = 6;
175
- }
176
-
177
- message dirInfoObj {
178
- string type = 1;
179
- string birthtime = 2;
180
- string name = 3;
181
- uint64 size = 4;
182
- uint64 count = 5;
183
- string server = 6;
184
- }
185
-
186
- message deleteOut {
187
- repeated deleteObjSuccess success = 1;
188
- repeated deleteObjError error = 2;
189
-
190
- }
191
-
192
- message deleteObjSuccess {
193
- string object = 1;
194
- string type = 2;
195
- string status = 3;
196
- }
197
-
198
- message deleteObjError {
199
- string object = 1;
200
- string fullPath = 2;
201
- string result = 3;
202
- }
203
-
204
- message readDirIn {
205
- string dir = 1;
206
- repeated string filter = 2;
207
- string fullPath = 3;
208
- }
209
-
210
- message readDirOut {
211
- repeated string res = 1;
212
-
213
- }
214
-
215
- message downloadFileIn {
216
- string filePath = 1;
217
- }
218
-
219
- message downloadFileOut {
220
- bytes chunk_data = 1;
221
- }
222
-
223
-
224
- service Map {
225
- rpc Render(render_in) returns (render_out) {}
226
- rpc RenderXML(render_in) returns (render_out) {}
227
- rpc LoadXML(xml_in) returns (xml_out) {}
228
- rpc MarkerIcon(cmd_in) returns (cmd_out) {}
229
- rpc Gdal(gdal_in) returns (gdal_out) {}
230
- rpc ClearTiles(clear_tiles_in) returns (clear_tiles_out) {}
231
- rpc RenderStatus(empty) returns (statusInfo) {}
232
- rpc PrintMap(printMapIn) returns (printMapOut) {}
233
- rpc Log(log_in) returns (log_out) {}
234
- rpc SqlToShp(sqlToShpIn) returns (shapeOut) {}
235
- rpc uploadFile(fileIn) returns (fileManagerOut) {}
236
- rpc deleteFiles(deleteFileIn) returns (deleteOut) {}
237
- rpc checkFiles(checkFilesDir) returns (filesStatus) {}
238
- rpc fileList(fileListIn) returns (fileInfo) {}
239
- rpc readDir(readDirIn) returns (readDirOut) {}
240
- rpc downloadFile(downloadFileIn) returns (stream downloadFileOut) {}
241
- }
1
+ syntax = "proto3";
2
+
3
+
4
+ message render_in {
5
+ string path = 1;
6
+ string name = 2;
7
+ repeated double bbox = 3 [packed=true];
8
+ string tile = 4;
9
+ string xml = 5;
10
+ int32 width = 6;
11
+ int32 height = 7;
12
+ }
13
+
14
+
15
+ message render_out {
16
+ string err = 1;
17
+ string base64 = 2;
18
+ string tile = 3;
19
+ }
20
+
21
+ message xml_in {
22
+ string path = 1;
23
+ string name = 2;
24
+ string xml = 3;
25
+ bool reload = 4;
26
+ }
27
+
28
+ message xml_out {
29
+ string err = 1;
30
+ bool is_ok = 2;
31
+ }
32
+
33
+ message cmd_in {
34
+ string cmd = 1;
35
+ string svg = 2;
36
+ string filename = 3;
37
+ }
38
+
39
+ message cmd_out {
40
+ string err = 1;
41
+ string base64 = 2;
42
+ }
43
+
44
+ message gdal_in {
45
+ string name = 1;
46
+ string path = 2;
47
+ string out = 3;
48
+ string params = 4;
49
+ }
50
+ message gdal_out {
51
+ string result = 1;
52
+ string err = 2;
53
+ }
54
+
55
+ message clear_tiles_in {
56
+ string path = 1;
57
+ }
58
+
59
+ message clear_tiles_out {
60
+ oneof clear_message {
61
+ bool ok = 1;
62
+ string err = 2;
63
+ }
64
+ }
65
+
66
+ message empty {}
67
+
68
+ message statusRenderInfo {
69
+ string name = 1;
70
+ bool isActive = 2;
71
+ float workTime = 3;
72
+ }
73
+
74
+ message statusInfo {
75
+ repeated statusRenderInfo info = 1;
76
+ }
77
+
78
+ message printMapOut {
79
+ string map = 1;
80
+ string err = 2;
81
+ }
82
+
83
+ message xyz {
84
+ float x = 1;
85
+ float y = 2;
86
+ float z = 3;
87
+ }
88
+
89
+ message printMapIn {
90
+ string baseurl = 1;
91
+ string baseImg = 2;
92
+ repeated string xml = 3;
93
+ string overlayXml = 4;
94
+ string overlay = 5;
95
+ int32 width = 6;
96
+ int32 height = 7;
97
+ repeated double bbox = 8;
98
+ xyz pos = 9;
99
+ string geojsonSettings = 10;
100
+ }
101
+
102
+ message log_in {
103
+ uint32 rows = 1;
104
+ string level = 2;
105
+ }
106
+
107
+ message log_out {
108
+ repeated string logs = 1;
109
+ }
110
+
111
+ message sqlToShpIn {
112
+ string db = 1;
113
+ string host = 2;
114
+ string port = 3;
115
+ string sql = 4;
116
+ string savePath = 5;
117
+ string shpName = 6;
118
+ }
119
+
120
+ message shapeOut {
121
+ string zipPath = 1;
122
+ }
123
+
124
+ message fileIn {
125
+ bytes fileBytes = 1;
126
+ string relativeFilepath = 2;
127
+ }
128
+
129
+ message deleteFileIn {
130
+ string dir = 1;
131
+ repeated string objectList = 2;
132
+ }
133
+
134
+ message fileManagerOut {
135
+ bool status = 1;
136
+ string operation = 2;
137
+ }
138
+
139
+ message checkFilesDir {
140
+ string dir = 1;
141
+ repeated string filesSearch = 2;
142
+ }
143
+
144
+ enum status {
145
+ LOADING = 0;
146
+ OK = 1;
147
+ }
148
+
149
+ message filesStatus {
150
+ status fileLoadingStatus = 1;
151
+ }
152
+
153
+ message fileListIn {
154
+ string dir = 1;
155
+ }
156
+
157
+ message fileInfo {
158
+ repeated fileInfoL fileInfoList = 1;
159
+ }
160
+
161
+ message fileInfoL {
162
+ oneof fileInfo {
163
+ fileInfoObj file = 1;
164
+ dirInfoObj dir = 2;
165
+ }
166
+ }
167
+
168
+ message fileInfoObj {
169
+ string type = 1;
170
+ string birthtime = 2;
171
+ string name = 3;
172
+ uint64 size = 4;
173
+ string ext = 5;
174
+ string server = 6;
175
+ }
176
+
177
+ message dirInfoObj {
178
+ string type = 1;
179
+ string birthtime = 2;
180
+ string name = 3;
181
+ uint64 size = 4;
182
+ uint64 count = 5;
183
+ string server = 6;
184
+ }
185
+
186
+ message deleteOut {
187
+ repeated deleteObjSuccess success = 1;
188
+ repeated deleteObjError error = 2;
189
+
190
+ }
191
+
192
+ message deleteObjSuccess {
193
+ string object = 1;
194
+ string type = 2;
195
+ string status = 3;
196
+ }
197
+
198
+ message deleteObjError {
199
+ string object = 1;
200
+ string fullPath = 2;
201
+ string result = 3;
202
+ }
203
+
204
+ message readDirIn {
205
+ string dir = 1;
206
+ repeated string filter = 2;
207
+ string fullPath = 3;
208
+ }
209
+
210
+ message readDirOut {
211
+ repeated string res = 1;
212
+
213
+ }
214
+
215
+ message downloadFileIn {
216
+ string filePath = 1;
217
+ }
218
+
219
+ message downloadFileOut {
220
+ bytes chunk_data = 1;
221
+ }
222
+
223
+
224
+ service Map {
225
+ rpc Render(render_in) returns (render_out) {}
226
+ rpc RenderXML(render_in) returns (render_out) {}
227
+ rpc LoadXML(xml_in) returns (xml_out) {}
228
+ rpc MarkerIcon(cmd_in) returns (cmd_out) {}
229
+ rpc Gdal(gdal_in) returns (gdal_out) {}
230
+ rpc ClearTiles(clear_tiles_in) returns (clear_tiles_out) {}
231
+ rpc RenderStatus(empty) returns (statusInfo) {}
232
+ rpc PrintMap(printMapIn) returns (printMapOut) {}
233
+ rpc Log(log_in) returns (log_out) {}
234
+ rpc SqlToShp(sqlToShpIn) returns (shapeOut) {}
235
+ rpc uploadFile(fileIn) returns (fileManagerOut) {}
236
+ rpc deleteFiles(deleteFileIn) returns (deleteOut) {}
237
+ rpc checkFiles(checkFilesDir) returns (filesStatus) {}
238
+ rpc fileList(fileListIn) returns (fileInfo) {}
239
+ rpc readDir(readDirIn) returns (readDirOut) {}
240
+ rpc downloadFile(downloadFileIn) returns (stream downloadFileOut) {}
241
+ }
@@ -15,6 +15,7 @@ import addService from './services/add.service.js';
15
15
  import addGisRegistry from './registers/add.registry.js';
16
16
  import deleteGisRegistry from './registers/del.registry.js';
17
17
  import getServicesCol from './services/get.services.col.js';
18
+ import legendAuto from './services/legend.auto.js';
18
19
 
19
20
  async function route(app) {
20
21
  app.put('/insert-columns/:token', insertColumns);
@@ -38,6 +39,7 @@ async function route(app) {
38
39
  app.post('/gis-service/:id?', { config: { policy: ['public'] } }, addService);
39
40
  app.put('/gis-service/:id', { config: { policy: ['public'] } }, addService);
40
41
  app.delete('/gis-service/:id', { config: { policy: ['public'] } }, deleteService);
42
+ app.get('/legend-auto/:id', { config: { policy: ['public'] } }, legendAuto);
41
43
  }
42
44
 
43
45
  export default route;
@@ -1,10 +1,25 @@
1
- import { getSelectVal, getSelect } from '@opengis/fastify-table/utils.js';
1
+ import { getSelectVal, getSelect, getTemplates } from '@opengis/fastify-table/utils.js';
2
2
 
3
- export async function attachClassifiers(rowOrRows, classifiers = []) {
3
+ export async function attachClassifiers(rowOrRows, classifiers, table) {
4
4
  const rows = Array.isArray(rowOrRows) ? rowOrRows : [rowOrRows];
5
5
 
6
+ const list = getTemplates(['cls', 'select']);
7
+
8
+ if (table) {
9
+ await Promise.all(rows.map(async row => {
10
+ await Promise.all(Object.keys(row).filter(key => list.map(el => el[0]).includes(`${table}.${key}`)).map(async key => {
11
+ const arr = await getSelectVal({ name: `${table}.${key}`, values: [row[key]], ar: true });
12
+ const val = arr?.find?.(el => el.id === row[key]);
13
+ if (val?.text) { row[`${key}_data`] = val; }
14
+ row[key] = val?.text || val || row[key]; // for frontend
15
+ row[`${key}_text`] = val?.text || val || row[key]; // legacy?
16
+ }));
17
+ }));
18
+ return rowOrRows;
19
+ }
20
+
6
21
  await Promise.all(rows.map(async row => {
7
- classifiers.filter(item => item.classifier && item.name && row[item.name]).map(async ({ name, classifier }) => {
22
+ (classifiers || []).filter(item => item.classifier && item.name && row[item.name]).map(async ({ name, classifier }) => {
8
23
  const arr = await getSelectVal({ name: classifier, values: [row[name]], ar: true });
9
24
  const val = arr?.find?.(el => el.id === row[name]);
10
25
  if (val?.text) { row[`${name}_data`] = val; }
@@ -49,10 +49,12 @@ export async function handleRegistryRequest({ settings, query, object_id, offset
49
49
  //if (!row) return reply.code(404).send({ message: 'Object not found', status: 404 });
50
50
  if (!row) throw new Error('Object not found');
51
51
 
52
- await attachClassifiers(row, classifiers);
52
+ await attachClassifiers(row, classifiers, table_name);
53
53
 
54
54
  return {
55
55
  row,
56
+ card,
57
+ d: 1,
56
58
  columns: visibleColumns,
57
59
  register_id,
58
60
  name,
@@ -86,7 +88,7 @@ export async function handleRegistryRequest({ settings, query, object_id, offset
86
88
  pg.query(totalQuery),
87
89
  ]);
88
90
 
89
- const rows = await attachClassifiers(dataRes.rows, classifiers);
91
+ const rows = await attachClassifiers(dataRes.rows, classifiers, table_name);
90
92
  const total = parseInt(countRes.rows[0]?.count || 0, 10);
91
93
 
92
94
  const listConfig = {};
@@ -1,5 +1,23 @@
1
- import { dataInsert, dataUpdate, pgClients } from "@opengis/fastify-table/utils.js";
1
+ import { dataDelete, dataInsert, dataUpdate, pgClients } from "@opengis/fastify-table/utils.js";
2
2
 
3
+ async function updateTemplate(client, body, name, uid, id) {
4
+ if (!body || !name || !id) return;
5
+
6
+ await dataDelete({
7
+ pg: client,
8
+ id,
9
+ table: 'admin.templates',
10
+ uid,
11
+ });
12
+ const rowCount = await dataInsert({
13
+ pg: client,
14
+ id,
15
+ table: 'admin.templates',
16
+ data: { body, name },
17
+ uid,
18
+ }).then(el => el.rowCount);
19
+ return rowCount;
20
+ }
3
21
  export default async function addService({
4
22
  method, params = {}, body, pg = pgClients.client, user = {},
5
23
  }, reply) {
@@ -7,24 +25,41 @@ export default async function addService({
7
25
  return reply.status(400).send('not enough params: id');
8
26
  }
9
27
 
10
- if (method === 'POST') {
11
- const { rows = [] } = await dataInsert({
12
- pg,
28
+ const { uid = '0' } = user;
29
+
30
+ const client = await pg.connect();
31
+ try {
32
+ await client.query('begin');
33
+
34
+ if (method === 'POST') {
35
+ const { rows = [] } = await dataInsert({
36
+ pg: client,
37
+ id: params.id,
38
+ table: 'gis.services',
39
+ data: body,
40
+ uid,
41
+ });
42
+ const istemplate = await updateTemplate(client, body.html, body.template, uid, rows[0]?.service_id);
43
+ await client.query('commit');
44
+ return reply.status(200).send(rows[0]);
45
+ }
46
+
47
+
48
+ const row = await dataUpdate({
49
+ pg: client,
13
50
  id: params.id,
14
51
  table: 'gis.services',
15
52
  data: body,
16
- uid: user?.uid,
53
+ uid,
17
54
  });
18
- return reply.status(200).send(rows[0]);
19
- }
55
+ const istemplate = await updateTemplate(client, body.html, body.template, uid, params.id);
56
+ await client.query('commit');
57
+ return reply.status(200).send(row);
20
58
 
21
- const row = await dataUpdate({
22
- pg,
23
- id: params.id,
24
- table: 'gis.services',
25
- data: body,
26
- uid: user?.uid,
27
- });
28
-
29
- return reply.status(200).send(row);
59
+ } catch (err) {
60
+ await client.query('rollback');
61
+ return reply.status(500).send(err.toString());
62
+ } finally {
63
+ client.release();
64
+ }
30
65
  }
@@ -1,4 +1,4 @@
1
- import { getMeta, pgClients, yml2json } from "@opengis/fastify-table/utils.js";
1
+ import { dataUpdate, getMeta, pgClients, yml2json } from "@opengis/fastify-table/utils.js";
2
2
 
3
3
  const table = 'gis.services';
4
4
 
@@ -13,20 +13,43 @@ const columnType = {
13
13
  };
14
14
 
15
15
  export default async function getServices({ params = {}, pg = pgClients.client }, reply) {
16
- const { columns = [] } = await getMeta({ pg, table }) || {};
17
16
 
18
- const fields = columns.map(({ name, dataTypeID, title }) => ({ name, type: columnType[pg.pgType?.[dataTypeID] || 'text'], label: title || name }));
19
17
 
20
18
  const rows = await pg.query(`
21
19
  SELECT
22
20
  service_id, service_key, name, description, keywords, category, holder, group_id, service_type,
23
21
  source_type, service_url, source_path, query, geom_type, geometry_column, sql_list, attributes, filters,
24
- popup, style, legend, card, srid, st_asgeojson(bbox)::json as bbox, st_asgeojson(center)::json as center, is_active as enabled, is_public, is_active, is_downloadable, metadata,
25
- metadata_url, thumbnail_url, created_by, updated_by, updated_at, created_at
22
+ popup, style, legend, card, srid, bbox::box2d as extent, st_asgeojson(bbox)::json as bbox, st_asgeojson(center)::json as center, is_active as enabled, is_public, is_active, is_downloadable, metadata,
23
+ metadata_url, thumbnail_url, created_by, updated_by, updated_at, created_at, template
26
24
  FROM gis.services where ${params.id ? 'service_id=$1' : '1=1'}
27
25
  `, [params.id].filter(Boolean)).then(el => el.rows || []);
28
26
 
27
+ if (params.id && rows[0]?.template) {
28
+ const html = await pg.query('select body from admin.templates where template_id=$1', [params.id]).then(el => el.rows?.[0]?.body);
29
+ Object.assign(rows[0], { html });
30
+ }
31
+
32
+ const { columns = [] } = await getMeta({ pg, table: rows[0].source_path }) || {};
33
+
34
+ const fields = columns.map(({ name, dataTypeID, title }) => ({ name, type: columnType[pg.pgType?.[dataTypeID] || 'text'], label: title || name }));
35
+
36
+ const noCenterIds = rows.filter(row => !row.center && row.bbox).map(row => row.service_id);
37
+
38
+ 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 || {}) : {};
39
+
40
+ await Promise.all(rows.filter(row => centers[row.service_id]).map(async (row) => {
41
+ // console.log('update service center', row.service_id, JSON.stringify(centers[row.service_id]));
42
+ await dataUpdate({ pg, id: row.service_id, table: 'gis.services', data: { center: centers[row.service_id] } });
43
+ Object.assign(row, { center: centers[row.service_id] });
44
+ }));
45
+
29
46
  rows.forEach(row => Object.assign(row, { style: row.style ? yml2json(row.style) : undefined }));
47
+ rows.forEach(row => Object.assign(row, { center: row.center?.coordinates }));
48
+ rows.filter(row => row.extent).forEach(row => Object.assign(row, {
49
+ extent: row.extent.match(/BOX\(([^)]+)\)/)?.[1]
50
+ ?.replace?.(/ /g, ",")
51
+ ?.split?.(",")
52
+ }));
30
53
 
31
54
  if (params.id && !rows.length) {
32
55
  return reply.status(404).send('service not found');