@opengis/gis 0.2.176 → 0.2.178

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 (164) hide show
  1. package/README.md +181 -181
  2. package/dist/{CardIcon-c1Q2M3wQ.js → CardIcon-CMRD1LLh.js} +1 -1
  3. package/dist/{EntityTablePage-B5gpkH4S.js → EntityTablePage-XNNCltAN.js} +2 -2
  4. package/dist/{ExtentOutlineLayer.vue_vue_type_script_setup_true_lang-DnGSvmMU.js → ExtentOutlineLayer.vue_vue_type_script_setup_true_lang-BqUjKTDY.js} +1 -1
  5. package/dist/{HeaderActions.vue_vue_type_script_setup_true_lang-C2B61JP6.js → HeaderActions.vue_vue_type_script_setup_true_lang-BXQ3hKwa.js} +1 -1
  6. package/dist/{MapSettings-6-Xru5Q8.js → MapSettings-P3h-vHn3.js} +2 -2
  7. package/dist/{RastersTablePage-CXNOcTy-.js → RastersTablePage-DnhGZLiH.js} +2 -2
  8. package/dist/{cartocss-BFVqWvfP.js → cartocss-BkrOeQg4.js} +6 -6
  9. package/dist/{import-utils-uxk4LdUJ.js → import-utils-BCATsbB5.js} +1 -1
  10. package/dist/{index-CahlV-Xs.js → index-cUagVqgA.js} +39 -39
  11. package/dist/index.css +1 -1
  12. package/dist/index.js +1 -1
  13. package/dist/index.umd.cjs +5 -5
  14. package/dist/{raster-CEi56f0P.js → raster-CP0tUp3j.js} +3 -3
  15. package/dist/{register-NFAGBNa3.js → register-Cr-uQcby.js} +3 -3
  16. package/dist/{service-BYspag8U.js → service-DwJaKrOd.js} +6 -6
  17. package/dist/{vs-datatable-CHcBr6zy.js → vs-datatable-B9WoVk-b.js} +2 -2
  18. package/module/gis/cls/service_type.json +42 -42
  19. package/module/gis/form/gis.registers.form.json +127 -127
  20. package/module/test/map/addr.json +302 -302
  21. package/module/test/map/addr1.yml +23 -23
  22. package/module/test/select/address.str_id.sql +3 -3
  23. package/package.json +77 -77
  24. package/plugin.js +64 -64
  25. package/server/migrations/array_intersect.sql +13 -13
  26. package/server/migrations/cartocss.sql +27 -27
  27. package/server/migrations/group_list.sql +74 -74
  28. package/server/migrations/maps.sql +30 -30
  29. package/server/migrations/metadata.sql +415 -415
  30. package/server/migrations/ogc.sql +106 -106
  31. package/server/migrations/rasters.sql +265 -265
  32. package/server/migrations/services.sql +247 -247
  33. package/server/migrations/services_users_rel.sql +22 -22
  34. package/server/migrations/widgets.sql +20 -20
  35. package/server/plugins/crons.js +21 -21
  36. package/server/plugins/mapnik/funcs/checkRasterFile.js +107 -107
  37. package/server/plugins/mapnik/funcs/mapnik.js +151 -151
  38. package/server/plugins/mapnik/funcs/rootFolder.mjs +8 -8
  39. package/server/plugins/mapnik/map.proto +437 -437
  40. package/server/plugins/vite.js +74 -74
  41. package/server/routes/cartocss/cartocss.route.js +31 -31
  42. package/server/routes/cartocss/cartocss.route.test.js +78 -78
  43. package/server/routes/cartocss/cartocss.schema.js +73 -73
  44. package/server/routes/cartocss/functions/add.cartocss.js +39 -39
  45. package/server/routes/cartocss/functions/get.cartocss.js +57 -57
  46. package/server/routes/gis/functions/checkXML.js +107 -107
  47. package/server/routes/gis/functions/dashboard.js +24 -24
  48. package/server/routes/gis/gis.route.js +26 -26
  49. package/server/routes/gis/gis.route.test.js +33 -33
  50. package/server/routes/map/functions/clearTiles.js +35 -35
  51. package/server/routes/map/functions/geojson.js +187 -187
  52. package/server/routes/map/functions/gis.convert.js +65 -65
  53. package/server/routes/map/functions/gis.icon.js +220 -220
  54. package/server/routes/map/functions/jsonData.js +206 -206
  55. package/server/routes/map/functions/layerList.js +102 -102
  56. package/server/routes/map/functions/map.js +122 -122
  57. package/server/routes/map/functions/mapCatalog.js +72 -72
  58. package/server/routes/map/functions/mapCatalogAttribute.js +53 -53
  59. package/server/routes/map/functions/mapFeatures.js +133 -133
  60. package/server/routes/map/functions/mapFormat.js +232 -232
  61. package/server/routes/map/functions/mapTiles.js +162 -162
  62. package/server/routes/map/functions/maps.js +15 -15
  63. package/server/routes/map/functions/marker_icon.js +43 -43
  64. package/server/routes/map/functions/parseGeoFile.js +85 -85
  65. package/server/routes/map/functions/pointHeight.js +51 -51
  66. package/server/routes/map/functions/vtile.js +174 -174
  67. package/server/routes/map/functions/vtile1.js +185 -185
  68. package/server/routes/map/map.route.js +280 -280
  69. package/server/routes/map/map.route.test.js +503 -503
  70. package/server/routes/map/map.schema.js +356 -356
  71. package/server/routes/map/pin/circle1.svg +76 -76
  72. package/server/routes/map/pin/circle2.svg +18 -18
  73. package/server/routes/map/pin/none.svg +12 -12
  74. package/server/routes/map/pin/pin.svg +14 -14
  75. package/server/routes/map/pin/pin1.svg +16 -16
  76. package/server/routes/map/pin/pin2.svg +7 -7
  77. package/server/routes/map/pin/pin3.svg +34 -34
  78. package/server/routes/map/pin/pin4.svg +14 -14
  79. package/server/routes/map/pin/pin5.svg +7 -7
  80. package/server/routes/map/pin/pin6.svg +12 -12
  81. package/server/routes/map/pin/pin7.svg +12 -12
  82. package/server/routes/mapnik/functions/cartoBounds.js +32 -32
  83. package/server/routes/mapnik/functions/clearTiles.js +103 -103
  84. package/server/routes/mapnik/functions/convertBoundToTile.js +27 -27
  85. package/server/routes/mapnik/functions/createXml.js +76 -76
  86. package/server/routes/mapnik/functions/createXmlMulti.js +72 -72
  87. package/server/routes/mapnik/functions/fileSearch.js +35 -35
  88. package/server/routes/mapnik/functions/fileStat.js +28 -28
  89. package/server/routes/mapnik/functions/pretile.js +138 -138
  90. package/server/routes/mapnik/functions/rasterInfo.js +66 -66
  91. package/server/routes/mapnik/functions/readDir.js +22 -22
  92. package/server/routes/mapnik/functions/rtile.js +164 -164
  93. package/server/routes/mapnik/functions/uploadRaster.js +152 -152
  94. package/server/routes/mapnik/functions/uploadXML.js +140 -140
  95. package/server/routes/mapnik/mapnik.route.js +102 -102
  96. package/server/routes/mapnik/mapnik.route.test.js +370 -370
  97. package/server/routes/mapnik/mapnik.schema.mjs +195 -195
  98. package/server/routes/mapnik.prefixless/functions/mapnikLogger.js +31 -31
  99. package/server/routes/mapnik.prefixless/functions/mapnikStat.js +21 -21
  100. package/server/routes/mapnik.prefixless/mapnik.prefixless.route.js +20 -20
  101. package/server/routes/mapnik.prefixless/mapnik.prefixless.route.test.js +91 -91
  102. package/server/routes/mapnik.prefixless/mapnik.prefixless.schema.mjs +27 -27
  103. package/server/routes/maps/functions/add.map.js +42 -42
  104. package/server/routes/maps/functions/del.map.js +17 -17
  105. package/server/routes/maps/functions/get.map.js +119 -119
  106. package/server/routes/maps/maps.route.js +58 -58
  107. package/server/routes/maps/maps.route.test.js +231 -231
  108. package/server/routes/maps/maps.schema.js +9 -9
  109. package/server/routes/metadata/functions/editMetadata.js +35 -35
  110. package/server/routes/metadata/functions/metadataXML.js +19 -19
  111. package/server/routes/metadata/metadata.route.js +26 -26
  112. package/server/routes/metadata/metadata.route.test.js +46 -46
  113. package/server/routes/metadata/metadata.schema.js +53 -53
  114. package/server/routes/ogc/functions/map.info.point.js +117 -117
  115. package/server/routes/ogc/ogc.route.js +21 -21
  116. package/server/routes/ogc/ogc.route.test.js +53 -53
  117. package/server/routes/ogc/ogc.schema.js +16 -16
  118. package/server/routes/permissions/functions/catalog.permissions.edit.js +20 -20
  119. package/server/routes/permissions/functions/catalog.permissions.js +12 -12
  120. package/server/routes/permissions/functions/get.permissions.js +59 -59
  121. package/server/routes/permissions/functions/gis.catalog.js +80 -80
  122. package/server/routes/permissions/permissions.route.js +35 -35
  123. package/server/routes/permissions/permissions.route.test.js +35 -35
  124. package/server/routes/permissions/permissions.schema.js +43 -43
  125. package/server/routes/registers/functions/add.registry.js +37 -37
  126. package/server/routes/registers/functions/classifiers.js +100 -100
  127. package/server/routes/registers/functions/columns.js +3 -3
  128. package/server/routes/registers/functions/content.type.js +9 -9
  129. package/server/routes/registers/functions/del.registry.js +14 -14
  130. package/server/routes/registers/functions/get.info.js +93 -93
  131. package/server/routes/registers/functions/getFilterSQLParametrized/formatValue.js +312 -0
  132. package/server/routes/registers/functions/getFilterSQLParametrized/getFilterQuery.js +126 -0
  133. package/server/routes/registers/functions/getFilterSQLParametrized/getRangeQuery.js +353 -0
  134. package/server/routes/registers/functions/getFilterSQLParametrized/index.js +81 -0
  135. package/server/routes/registers/functions/getTableColumnMeta.js +125 -0
  136. package/server/routes/registers/functions/gis.export.js +159 -159
  137. package/server/routes/registers/functions/gis.registry.js +71 -71
  138. package/server/routes/registers/functions/gis.registry.list.js +61 -61
  139. package/server/routes/registers/functions/gis.suggest.js +275 -0
  140. package/server/routes/registers/functions/handleRegistryRequest.js +175 -147
  141. package/server/routes/registers/functions/map.registry.js +87 -87
  142. package/server/routes/registers/registers.route.js +91 -80
  143. package/server/routes/registers/registers.route.test.js +173 -144
  144. package/server/routes/registers/registers.schema.js +275 -246
  145. package/server/routes/root.mjs +3 -3
  146. package/server/routes/services/functions/add.service.js +62 -62
  147. package/server/routes/services/functions/create.route.js +93 -93
  148. package/server/routes/services/functions/del.service.js +12 -12
  149. package/server/routes/services/functions/get.layer.geom.js +27 -27
  150. package/server/routes/services/functions/get.service.users.js +76 -76
  151. package/server/routes/services/functions/get.services.col.js +34 -34
  152. package/server/routes/services/functions/get.services.js +89 -89
  153. package/server/routes/services/functions/legend.auto.js +77 -77
  154. package/server/routes/services/services.route.js +76 -76
  155. package/server/routes/services/services.route.test.js +167 -167
  156. package/server/routes/services/services.schema.js +285 -278
  157. package/server/routes/widgets/functions/add.widget.js +38 -38
  158. package/server/routes/widgets/functions/del.widget.js +17 -17
  159. package/server/routes/widgets/functions/get.widget.js +41 -41
  160. package/server/routes/widgets/widgets.route.js +61 -61
  161. package/server/routes/widgets/widgets.route.test.js +144 -144
  162. package/server/routes/widgets/widgets.schema.js +24 -24
  163. package/server/shared/listFiles.js +14 -14
  164. package/utils.js +13 -13
@@ -1,503 +1,503 @@
1
- import { describe, test, beforeAll, expect } from 'vitest';
2
-
3
- import { getTemplate, getPGAsync, uploadFile, isFileExists } from '@opengis/fastify-table/utils.js';
4
-
5
- import { build, injectWithHeaders } from '../../../test/helper.js';
6
-
7
- const user = { id: '1', type: 'admin' };
8
-
9
- let pg;
10
- let addr = {};
11
-
12
- // base64 from ziped shapefile for /gis-convert api
13
- const base64 = `UEsDBBQAAAAAAMVDBV2t8XCrgwAAAIMAAAAJAAAAcG9pbnQucHJqR0VPR0NTWyJXR1MgODQiLERBVFVNWyJXR1NfMTk4NCIsU1BIRVJPSURbIldHUyA4NCIsNjM3ODEzNywyOTguMjU3MjIzNTYzXV0sUFJJTUVNWyJHcmVlbndpY2giLDBdLFVOSVRbImRlZ3JlZSIsMC4wMTc0NTMyOTI1MTk5NDMzXV1QSwMEFAAAAAAAxUMFXeUBOGx0AAAAdAAAAAkAAABwb2ludC5kYmYDfggFAQAAAEEAMwAAAAAAAAAAAAAAAAAAAAAAAAAAAGlkAAAAAAAAAAAATgAAAAAyAAAAAAAAAAAAAAAAAAAADSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMVBLAwQUAAAAAADFQwVdjfPd44AAAACAAAAACQAAAHBvaW50LnNocAAAJwoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEDoAwAAAQAAAPW52or9hT5A/fZ14Jw5SUD1udqK/YU+QP32deCcOUlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAACgEAAAD1udqK/YU+QP32deCcOUlAUEsDBBQAAAAAAMVDBV0HI9CqbAAAAGwAAAAJAAAAcG9pbnQuc2h4AAAnCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANugDAAABAAAA9bnaiv2FPkD99nXgnDlJQPW52or9hT5A/fZ14Jw5SUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADIAAAAKUEsBAhQDFAAAAAAAxUMFXa3xcKuDAAAAgwAAAAkAAAAAAAAAAAAAAKSBAAAAAHBvaW50LnByalBLAQIUAxQAAAAAAMVDBV3lAThsdAAAAHQAAAAJAAAAAAAAAAAAAACkgaoAAABwb2ludC5kYmZQSwECFAMUAAAAAADFQwVdjfPd44AAAACAAAAACQAAAAAAAAAAAAAApIFFAQAAcG9pbnQuc2hwUEsBAhQDFAAAAAAAxUMFXQcj0KpsAAAAbAAAAAkAAAAAAAAAAAAAAKSB7AEAAHBvaW50LnNoeFBLBQYAAAAABAAEANwAAAB/AgAAAAA=`;
14
-
15
- describe('map api test', () => {
16
- beforeAll(async () => {
17
- await build();
18
- pg = await getPGAsync();
19
-
20
- // preload an address row for map-feature tests
21
- if (pg.pk?.['data_address.address']) {
22
- addr = await pg
23
- .query('select addr_id, st_x(geom), st_y(geom) from data_address.address where geom is not null limit 1')
24
- .then(r => r.rows?.[0] || {});
25
- expect(!!addr.addr_id).toBeTruthy();
26
- }
27
- });
28
-
29
- test('GET /map-catalog', async () => {
30
- const res = await injectWithHeaders({ method: 'GET', url: '/api/map-catalog' });
31
- expect(res.statusCode).toBe(200);
32
- const rep = res.json();
33
- expect(Array.isArray(rep.rasters)).toBeTruthy();
34
- expect(Array.isArray(rep.services)).toBeTruthy();
35
- });
36
-
37
- test('GET /marker-icon/*', async () => {
38
- // latest changes break local icon generation
39
- const res = await injectWithHeaders({
40
- method: 'GET',
41
- // url: `/api/marker-icon/pin6-m-fa-space-shuttle+teal.png`,
42
- url: '/api/marker-icon/icon-l-fa-location-dot+ff0000.svg',
43
- headers: { uid: user.id, user_type: user.type }
44
- });
45
- if (res.statusCode === 200) {
46
- expect(res.statusCode).toBe(200);
47
- expect(res.body.includes('PNG')).toBeTruthy();
48
- expect(res.rawPayload?.length).toBeTruthy();
49
- } else {
50
- expect(res.statusCode).toBe(404);
51
- expect(res.body.message).toMatch('icon not found');
52
- }
53
- }, 15000);
54
-
55
- test('GET /gis-icon/*', async () => {
56
- const res = await injectWithHeaders({
57
- method: 'GET',
58
- url: `/api/gis-icon/pin6-m-fa-plus-square+7ed957.png`,
59
- headers: { uid: user.id, user_type: user.type }
60
- });
61
- expect(res.statusCode).toBe(200);
62
- expect(res.headers['content-type']).toBe('image/png');
63
- expect(res.body.includes('PNG')).toBeTruthy();
64
- });
65
-
66
- test('GET /icon/*', async () => {
67
- const res = await injectWithHeaders({
68
- method: 'GET',
69
- url: `/api/icon/pin6-m-fa-plus-square+7ed957.png`,
70
- headers: { uid: user.id, user_type: user.type }
71
- });
72
- expect(res.statusCode).toBe(200);
73
- expect(res.headers['content-type']).toBe('image/png');
74
- expect(res.body.includes('PNG')).toBeTruthy();
75
- });
76
-
77
- test('GET /gis-icon/*.svg', async () => {
78
- const res = await injectWithHeaders({
79
- method: 'GET',
80
- url: `/api/gis-icon/pin6-m-fa-plus-square+7ed957.svg`,
81
- headers: { uid: user.id, user_type: user.type }
82
- });
83
- expect(res.statusCode).toBe(200);
84
- expect(res.headers['content-type']).toBe('image/svg+xml');
85
- expect(res.body).toContain('<svg');
86
- expect(res.body).toContain('viewBox="0 0 446 518"');
87
- });
88
-
89
- test('GET /gis-icon/ar-* renders a symbol', async () => {
90
- const res = await injectWithHeaders({
91
- method: 'GET',
92
- url: `/api/gis-icon/pin6-m-ar-E+ff4500.svg`,
93
- headers: { uid: user.id, user_type: user.type }
94
- });
95
- expect(res.statusCode).toBe(200);
96
- expect(res.headers['content-type']).toBe('image/svg+xml');
97
- expect(res.body).toContain('>E</text>');
98
- expect(res.body).toContain('fill="#ff4500"');
99
- expect(res.body).not.toContain('#ff0000');
100
- });
101
-
102
- test.each(['pin2', 'pin3', 'pin4', 'pin5', 'pin6', 'pin7'])(
103
- 'GET /gis-icon/%s recolors the background',
104
- async (pin) => {
105
- const res = await injectWithHeaders({
106
- method: 'GET',
107
- url: `/api/gis-icon/${pin}-m-fa-plus-square+7ed957.svg`,
108
- headers: { uid: user.id, user_type: user.type }
109
- });
110
- expect(res.statusCode).toBe(200);
111
- expect(res.body).not.toContain('#ff0000');
112
- expect(res.body).toContain('fill="#7ed957"');
113
- expect(res.body.lastIndexOf('data-gis-icon-layer="foreground"'))
114
- .toBeGreaterThan(res.body.lastIndexOf('<use'));
115
- },
116
- );
117
-
118
- test.each([
119
- ['pin', '#ff0000'],
120
- ['pin1', '#ff0000'],
121
- ['circle1', '#176e1c'],
122
- ['circle2', '#ff0000'],
123
- ['none', 'fill="red"'],
124
- ])(
125
- 'GET /gis-icon/%s keeps the original background',
126
- async (pin, backgroundColor) => {
127
- const res = await injectWithHeaders({
128
- method: 'GET',
129
- url: `/api/gis-icon/${pin}-m-fa-plus-square+7ed957.svg`,
130
- headers: { uid: user.id, user_type: user.type }
131
- });
132
- expect(res.statusCode).toBe(200);
133
- expect(res.body).toContain(backgroundColor);
134
- expect(res.body).toContain('fill="#7ed957"');
135
- expect(res.body.lastIndexOf('data-gis-icon-layer="foreground"'))
136
- .toBeGreaterThan(res.body.lastIndexOf('<use'));
137
- },
138
- );
139
-
140
- test('GET /maps', async () => {
141
- const res = await injectWithHeaders({
142
- method: 'GET', url: `/api/maps`,
143
- headers: { uid: user.id, user_type: user.type }
144
- });
145
- expect(res.statusCode).toBe(200);
146
- const rep = res.json();
147
- expect(rep).toBeInstanceOf(Array);
148
- if (rep?.length) {
149
- expect(rep?.[0]?.name).toBeTruthy();
150
- expect(rep?.[0]?.slug).toBeTruthy();
151
- }
152
- });
153
-
154
- test(`GET /map/:name`, async () => {
155
- const slug = 'addr';
156
- const body = await getTemplate('map', slug);
157
- expect(body).toBeTruthy();
158
-
159
- const res = await injectWithHeaders({
160
- method: 'GET', url: `/api/map/${slug}`,
161
- headers: { uid: user.id, user_type: user.type }
162
- });
163
- expect(res.statusCode).toBe(200);
164
- // expect(res.body).toHaveProperty('name');
165
- // expect(res.body.name).toBe('Адресний реєстр');
166
- expect(res.body).toHaveProperty('source_path');
167
- expect(res.body.source_path).toBe('data_address.street');
168
- expect(res.body).toHaveProperty('layers');
169
- expect(res.body.layers.length).toBe(3);
170
- expect(res.body).toHaveProperty('maps');
171
- expect(res.body.maps.length).toBeGreaterThan(0);
172
- });
173
-
174
- test(`GET /map-features/:slug/:id`, async () => {
175
- const slug = 'addr';
176
- const res = await injectWithHeaders({
177
- method: 'GET',
178
- url: `/api/map-features/${slug}/${addr.addr_id}`,
179
- });
180
- expect(res.statusCode).toBe(200);
181
- const rep = res.json();
182
- expect(rep.data).toBeTruthy();
183
- expect(rep.meta).toBeTruthy();
184
- expect(rep.data.addr_id).toBe(addr.addr_id);
185
- });
186
-
187
- test(`GET /map-features-point/:slug?x=&y=`, async () => {
188
- const slug = 'addr';
189
- const res = await injectWithHeaders({
190
- method: 'GET',
191
- url: `/api/map-features-point/${slug}?x=${addr.st_x}&y=${addr.st_y}`,
192
- headers: { uid: user.id, user_type: user.type }
193
- });
194
- expect(res.statusCode).toBe(200);
195
- const rep = res.json();
196
- expect(rep.data).toBeTruthy();
197
- expect(rep.meta).toBeTruthy();
198
- expect(rep.data.addr_id).toBe(addr.addr_id);
199
- });
200
-
201
- test(`GET /map-tiles/:slug/:z/:y/:x.pbf`, async () => {
202
- const slug = 'addr';
203
- const res = await injectWithHeaders({
204
- method: 'GET',
205
- url: `/api/map-tiles/${slug}/14/9767/5659.pbf`,
206
- headers: { uid: user.id, user_type: user.type }
207
- });
208
- expect(res.statusCode).toBe(200);
209
- expect(res.headers['content-type']).toBe('application/x-protobuf');
210
- expect(res.body.length).toBeGreaterThan(0);
211
- expect(res.body).toMatch('\u001a�8\n\u0006street');
212
- });
213
-
214
- test(`GET /api/map-catalog/:service/:attr`, async () => {
215
- const { id, columns } = await pg.query('select service_id as id, coalesce(filters::jsonb,attributes::jsonb,card::jsonb,popup::jsonb) as columns from gis.services where source_path is not null and is_active and is_public limit 1').then(r => r.rows?.[0] || {});
216
- expect(id).toBeTypeOf('string');
217
- expect(columns).toBeInstanceOf(Array);
218
- expect(columns.length).toBeGreaterThan(0);
219
- const { name: attr, data } = columns.find(col => col.data) || {};
220
- expect(attr).toBeTypeOf('string');
221
- expect(data).toBeTypeOf('string');
222
- const res = await injectWithHeaders({
223
- method: 'GET',
224
- url: `/api/map-catalog/${id}/${attr}`,
225
- headers: { uid: user.id, user_type: user.type }
226
- });
227
- expect(res.statusCode).toBe(200);
228
- expect(res.body).toHaveProperty('legend');
229
- expect(res.body.legend).toBeInstanceOf(Array);
230
- if (res.body.legend.length) {
231
- expect(res.body.legend[0]).toHaveProperty('id');
232
- expect(res.body.legend[0]).toHaveProperty('count');
233
- expect(res.body.legend[0].count).toMatch(/\d+/);
234
- expect(+res.body.legend[0].count).toBeGreaterThanOrEqual(0);
235
- }
236
- expect(res.body).toHaveProperty('min');
237
- expect(res.body).toHaveProperty('max');
238
- });
239
-
240
- test(`GET /api/map-catalog/:service/:attr?cls=`, async () => {
241
- const { id, columns } = await pg.query('select service_id as id, coalesce(filters::jsonb,attributes::jsonb,card::jsonb,popup::jsonb) as columns from gis.services where source_path is not null and is_active and is_public limit 1').then(r => r.rows?.[0] || {});
242
- expect(id).toBeTypeOf('string');
243
- expect(columns).toBeInstanceOf(Array);
244
- expect(columns.length).toBeGreaterThan(0);
245
- const { name: attr, data } = columns.find(col => col.data) || {};
246
- expect(attr).toBeTypeOf('string');
247
- expect(data).toBeTypeOf('string');
248
- const res = await injectWithHeaders({
249
- method: 'GET',
250
- url: `/api/map-catalog/${id}/${attr}`,
251
- query: { cls: data },
252
- headers: { uid: user.id, user_type: user.type }
253
- });
254
- expect(res.statusCode).toBe(200);
255
- expect(res.body).toHaveProperty('legend');
256
- expect(res.body.legend).toBeInstanceOf(Array);
257
- if (res.body.legend.length) {
258
- expect(res.body.legend[0]).toHaveProperty('id');
259
- expect(res.body.legend[0]).toHaveProperty('count');
260
- expect(res.body.legend[0].count).toMatch(/\d+/);
261
- expect(+res.body.legend[0].count).toBeGreaterThanOrEqual(0);
262
- expect(res.body.legend[0]).toHaveProperty('text');
263
- expect(res.body.legend[0].text).toBeTypeOf('string');
264
- }
265
- expect(res.body).toHaveProperty('min');
266
- expect(res.body).toHaveProperty('max');
267
- });
268
-
269
- test(`GET /api/vtile/:layer/:lang/:z/:y/:x`, async () => {
270
- const layer = await pg.query('select service_id from gis.services where source_path is not null and is_active and is_public limit 1').then(r => r.rows?.[0]?.service_id);
271
- expect(layer).toBeTypeOf('string');
272
- const res = await injectWithHeaders({
273
- method: 'GET',
274
- url: `/api/vtile/${layer}/ua/1/1/1.pbf`,
275
- // headers: { uid: user.id, user_type: user.type }
276
- });
277
- expect(res.statusCode).toBe(200);
278
- expect(res.headers).toHaveProperty('cache-control');
279
- expect(res.headers).toHaveProperty('content-type');
280
- expect(res.headers['content-type']).toBe("application/x-protobuf");
281
- expect(res.headers).toHaveProperty('content-length');
282
- });
283
-
284
- test(`GET /api/gis-clear-vtile/:id`, async () => {
285
- const layer = await pg.query('select service_id from gis.services where source_path is not null and is_active and is_public limit 1').then(r => r.rows?.[0]?.service_id);
286
- expect(layer).toBeTypeOf('string');
287
- const res = await injectWithHeaders({
288
- method: 'GET',
289
- url: `/api/gis-clear-vtile/${layer}`,
290
- headers: { uid: user.id, user_type: user.type }
291
- });
292
- expect(res.statusCode).toBe(200);
293
- expect(res.body).toHaveProperty('message');
294
- expect(res.body.message).toBe('Тайли очищено');
295
- });
296
- test(`GET /api/geojson/:layer`, async () => {
297
- const layer = await pg.query('select service_id from gis.services where source_path is not null and is_active and is_public limit 1').then(r => r.rows?.[0]?.service_id);
298
- expect(layer).toBeTypeOf('string');
299
- const res = await injectWithHeaders({
300
- method: 'GET',
301
- url: `/api/geojson/${layer}`,
302
- // headers: { uid: user.id, user_type: user.type }
303
- });
304
- expect(res.statusCode).toBe(200);
305
- expect(res.body).toHaveProperty('type');
306
- expect(res.body.type).toBe('FeatureCollection');
307
- expect(res.body).toHaveProperty('features');
308
- expect(res.body.features).toBeInstanceOf(Array);
309
- expect(res.body.features.length).toBeGreaterThan(0);
310
- expect(res.body.features[0]).toHaveProperty('type');
311
- expect(res.body.features[0].type).toBe('Feature');
312
- expect(res.body.features[0]).toHaveProperty('geometry');
313
- expect(res.body.features[0].geometry).toHaveProperty('type');
314
- expect(res.body.features[0].geometry.type).toBeOneOf(['Point', 'LineString', 'Polygon']);
315
- expect(res.body.features[0].geometry).toHaveProperty('coordinates');
316
- expect(res.body.features[0].geometry.coordinates).toBeInstanceOf(Array);
317
- expect(res.body.features[0]).toHaveProperty('properties');
318
- expect(Object.keys(res.body.features[0].properties).length).toBeGreaterThan(0);
319
- });
320
- test(`GET /api/json/:layer`, async () => {
321
- const layer = await pg.query('select register_id from gis.registers where 1=1 and is_active and is_public limit 1').then(r => r.rows?.[0]?.register_id);
322
- expect(layer).toBeTypeOf('string');
323
- const res = await injectWithHeaders({
324
- method: 'GET',
325
- url: `/api/json/${layer}`,
326
- // headers: { uid: user.id, user_type: user.type }
327
- });
328
- expect(res.statusCode).toBe(200);
329
- expect(res.body).toHaveProperty('register_id');
330
- expect(res.body).toHaveProperty('total');
331
- expect(res.body).toHaveProperty('limit');
332
- expect(res.body).toHaveProperty('page');
333
- expect(res.body).toHaveProperty('data');
334
- expect(res.body.data).toBeInstanceOf(Array);
335
- expect(res.body.data.length).toBeGreaterThan(0);
336
- expect(res.body.data[0]).toHaveProperty('id');
337
- });
338
- test(`GET /api/gis-layer-list`, async () => {
339
- const res = await injectWithHeaders({
340
- method: 'GET',
341
- url: `/api/gis-layer-list`,
342
- // headers: { uid: user.id, user_type: user.type }
343
- });
344
- expect(res.statusCode).toBe(200);
345
- });
346
-
347
- test(`GET /api/map-format?layer=&id=`, async () => {
348
- const { service_id: layer, source_path: table } = await pg.query('select service_id, source_path from gis.services where source_path is not null and is_active and is_public limit 1').then(r => r.rows?.[0] || {});
349
- expect(layer).toBeTypeOf('string');
350
- expect(pg.pk?.[table]).toBeTypeOf('string');
351
-
352
- const id = await pg.query(`select ${pg.pk?.[table]} as id from ${table} limit 1`).then(r => r.rows?.[0]?.id);
353
- expect(id).toBeTypeOf('string');
354
-
355
- const res = await injectWithHeaders({
356
- method: 'GET',
357
- url: `/api/map-format`,
358
- query: {
359
- layer,
360
- id
361
- },
362
- headers: { uid: user.id, user_type: user.type }
363
- });
364
- expect(res.statusCode).toBe(200);
365
- expect(res.body).toHaveProperty('id');
366
- expect(res.body).toHaveProperty('data');
367
- expect(res.body).toHaveProperty('columns');
368
- expect(res.body.columns).toBeInstanceOf(Array);
369
- expect(res.body.data).toHaveProperty('id');
370
- expect(res.body.data).toHaveProperty('geom');
371
- });
372
- test(`GET /api/map-format?layer=&lng=&lat=`, async () => {
373
- const { cartocss_id: layer, card_table: table } = await pg.query('select cartocss_id, card_table from gis.cartocss where card_table is not null and enabled and is_public limit 1').then(r => r.rows?.[0] || {});
374
- expect(layer).toBeTypeOf('string');
375
- expect(pg.pk?.[table]).toBeTypeOf('string');
376
-
377
- const { st_x: lng, st_y: lat } = await pg.query(`select st_x(st_pointonsurface(st_transform(geom,4326))), st_y(st_pointonsurface(st_transform(geom,4326))) from ${table} where geom is not null limit 1`).then(r => r.rows[0]);
378
- expect(lat).toBeTypeOf('number');
379
- const res = await injectWithHeaders({
380
- method: 'GET',
381
- url: `/api/map-format`,
382
- query: {
383
- layer,
384
- lng,
385
- lat
386
- },
387
- headers: { uid: user.id, user_type: user.type }
388
- });
389
- expect(res.statusCode).toBe(200);
390
- expect(res.body).toHaveProperty('id');
391
- expect(res.body).toHaveProperty('ids');
392
- expect(res.body.ids).toBeInstanceOf(Array);
393
- expect(res.body).toHaveProperty('data');
394
- expect(res.body).toHaveProperty('columns');
395
- expect(res.body.columns).toBeInstanceOf(Array);
396
- expect(res.body.data).toHaveProperty('id');
397
- expect(res.body.data).toHaveProperty('geom');
398
- expect(res.body.data).toHaveProperty('distance'); // self-explanatory
399
- expect(res.body.data).toHaveProperty('key'); // cartocss layer key
400
- });
401
- test(`GET /api/coords/height?lat=&lng=`, async () => {
402
- const res = await injectWithHeaders({
403
- method: 'GET',
404
- url: `/api/coords/height`,
405
- query: { lat: 50, lng: 30 },
406
- headers: { uid: user.id, user_type: user.type }
407
- });
408
- expect(res.statusCode).toBe(200);
409
- expect(res.body).toHaveProperty('height');
410
- expect(res.body.height).toBeTypeOf('number');
411
- expect(res.body.height).toBe(201);
412
- expect(res.body).toHaveProperty('lat');
413
- expect(res.body).toHaveProperty('lng');
414
- });
415
- test(`POST /api/gis-parse-geo`, async () => {
416
- const res = await injectWithHeaders({
417
- method: 'POST',
418
- url: `/api/gis-parse-geo`,
419
- payload: {
420
- content: `{ "type":"Feature", "geometry": { "type": "Point", "coordinates": [30.523,50.4501] }, "properties": {} }`
421
- },
422
- headers: { uid: user.id, user_type: user.type }
423
- });
424
- expect(res.statusCode).toBe(200);
425
- expect(res.body).toHaveProperty('type');
426
- expect(res.body.type).toBe('FeatureCollection');
427
- expect(res.body).toHaveProperty('features');
428
- expect(res.body.features).toBeInstanceOf(Array);
429
- expect(res.body.features.length).toBeGreaterThan(0);
430
- expect(res.body.features[0]).toHaveProperty('type');
431
- expect(res.body.features[0].type).toBe('Feature');
432
- expect(res.body.features[0]).toHaveProperty('geometry');
433
- expect(res.body.features[0].geometry).toHaveProperty('type');
434
- expect(res.body.features[0].geometry.type).toBeOneOf(['Point', 'LineString', 'Polygon']);
435
- expect(res.body.features[0].geometry).toHaveProperty('coordinates');
436
- expect(res.body.features[0].geometry.coordinates).toBeInstanceOf(Array);
437
- expect(res.body.features[0]).toHaveProperty('properties');
438
- });
439
- test(`POST /api/gis-convert (file as base64 + filename)`, async () => {
440
- const res = await injectWithHeaders({
441
- method: 'POST',
442
- url: `/api/gis-convert`,
443
- payload: {
444
- file: base64,
445
- filename: 'shapefile.zip',
446
- },
447
- headers: { uid: user.id, user_type: user.type }
448
- });
449
- expect(res.statusCode).toBe(200);
450
- expect(res.body).toHaveProperty('type');
451
- expect(res.body.type).toBe('FeatureCollection');
452
- expect(res.body).toHaveProperty('features');
453
- expect(res.body.features).toBeInstanceOf(Array);
454
- expect(res.body.features.length).toBeGreaterThan(0);
455
- expect(res.body.features[0]).toHaveProperty('type');
456
- expect(res.body.features[0].type).toBe('Feature');
457
- expect(res.body.features[0]).toHaveProperty('geometry');
458
- expect(res.body.features[0].geometry).toHaveProperty('type');
459
- expect(res.body.features[0].geometry.type).toBe('Point');
460
- expect(res.body.features[0].geometry).toHaveProperty('coordinates');
461
- expect(res.body.features[0].geometry.coordinates).toBeInstanceOf(Array);
462
- expect(res.body.features[0].geometry.coordinates[0]).toBe(30.5234);
463
- expect(res.body.features[0].geometry.coordinates[1]).toBe(50.4501);
464
- expect(res.body.features[0]).toHaveProperty('properties');
465
- expect(res.body.features[0].properties).toHaveProperty('id');
466
- expect(res.body.features[0].properties.id).toBe(1);
467
- });
468
-
469
- test(`POST /api/gis-convert (filepath)`, async () => {
470
- const relpath = '/files/test/shapefile.zip';
471
- const exists = await isFileExists(relpath);
472
- if (!exists) {
473
- await uploadFile(relpath, Buffer.from(base64, 'base64'));
474
- }
475
-
476
- const res = await injectWithHeaders({
477
- method: 'POST',
478
- url: `/api/gis-convert`,
479
- payload: {
480
- filepath: relpath
481
- },
482
- headers: { uid: user.id, user_type: user.type }
483
- });
484
- expect(res.statusCode).toBe(200);
485
- expect(res.body).toHaveProperty('type');
486
- expect(res.body.type).toBe('FeatureCollection');
487
- expect(res.body).toHaveProperty('features');
488
- expect(res.body.features).toBeInstanceOf(Array);
489
- expect(res.body.features.length).toBeGreaterThan(0);
490
- expect(res.body.features[0]).toHaveProperty('type');
491
- expect(res.body.features[0].type).toBe('Feature');
492
- expect(res.body.features[0]).toHaveProperty('geometry');
493
- expect(res.body.features[0].geometry).toHaveProperty('type');
494
- expect(res.body.features[0].geometry.type).toBe('Point');
495
- expect(res.body.features[0].geometry).toHaveProperty('coordinates');
496
- expect(res.body.features[0].geometry.coordinates).toBeInstanceOf(Array);
497
- expect(res.body.features[0].geometry.coordinates[0]).toBe(30.5234);
498
- expect(res.body.features[0].geometry.coordinates[1]).toBe(50.4501);
499
- expect(res.body.features[0]).toHaveProperty('properties');
500
- expect(res.body.features[0].properties).toHaveProperty('id');
501
- expect(res.body.features[0].properties.id).toBe(1);
502
- });
503
- });
1
+ import { describe, test, beforeAll, expect } from 'vitest';
2
+
3
+ import { getTemplate, getPGAsync, uploadFile, isFileExists } from '@opengis/fastify-table/utils.js';
4
+
5
+ import { build, injectWithHeaders } from '../../../test/helper.js';
6
+
7
+ const user = { id: '1', type: 'admin' };
8
+
9
+ let pg;
10
+ let addr = {};
11
+
12
+ // base64 from ziped shapefile for /gis-convert api
13
+ const base64 = `UEsDBBQAAAAAAMVDBV2t8XCrgwAAAIMAAAAJAAAAcG9pbnQucHJqR0VPR0NTWyJXR1MgODQiLERBVFVNWyJXR1NfMTk4NCIsU1BIRVJPSURbIldHUyA4NCIsNjM3ODEzNywyOTguMjU3MjIzNTYzXV0sUFJJTUVNWyJHcmVlbndpY2giLDBdLFVOSVRbImRlZ3JlZSIsMC4wMTc0NTMyOTI1MTk5NDMzXV1QSwMEFAAAAAAAxUMFXeUBOGx0AAAAdAAAAAkAAABwb2ludC5kYmYDfggFAQAAAEEAMwAAAAAAAAAAAAAAAAAAAAAAAAAAAGlkAAAAAAAAAAAATgAAAAAyAAAAAAAAAAAAAAAAAAAADSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMVBLAwQUAAAAAADFQwVdjfPd44AAAACAAAAACQAAAHBvaW50LnNocAAAJwoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEDoAwAAAQAAAPW52or9hT5A/fZ14Jw5SUD1udqK/YU+QP32deCcOUlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAACgEAAAD1udqK/YU+QP32deCcOUlAUEsDBBQAAAAAAMVDBV0HI9CqbAAAAGwAAAAJAAAAcG9pbnQuc2h4AAAnCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANugDAAABAAAA9bnaiv2FPkD99nXgnDlJQPW52or9hT5A/fZ14Jw5SUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADIAAAAKUEsBAhQDFAAAAAAAxUMFXa3xcKuDAAAAgwAAAAkAAAAAAAAAAAAAAKSBAAAAAHBvaW50LnByalBLAQIUAxQAAAAAAMVDBV3lAThsdAAAAHQAAAAJAAAAAAAAAAAAAACkgaoAAABwb2ludC5kYmZQSwECFAMUAAAAAADFQwVdjfPd44AAAACAAAAACQAAAAAAAAAAAAAApIFFAQAAcG9pbnQuc2hwUEsBAhQDFAAAAAAAxUMFXQcj0KpsAAAAbAAAAAkAAAAAAAAAAAAAAKSB7AEAAHBvaW50LnNoeFBLBQYAAAAABAAEANwAAAB/AgAAAAA=`;
14
+
15
+ describe('map api test', () => {
16
+ beforeAll(async () => {
17
+ await build();
18
+ pg = await getPGAsync();
19
+
20
+ // preload an address row for map-feature tests
21
+ if (pg.pk?.['data_address.address']) {
22
+ addr = await pg
23
+ .query('select addr_id, st_x(geom), st_y(geom) from data_address.address where geom is not null limit 1')
24
+ .then(r => r.rows?.[0] || {});
25
+ expect(!!addr.addr_id).toBeTruthy();
26
+ }
27
+ });
28
+
29
+ test('GET /map-catalog', async () => {
30
+ const res = await injectWithHeaders({ method: 'GET', url: '/api/map-catalog' });
31
+ expect(res.statusCode).toBe(200);
32
+ const rep = res.json();
33
+ expect(Array.isArray(rep.rasters)).toBeTruthy();
34
+ expect(Array.isArray(rep.services)).toBeTruthy();
35
+ });
36
+
37
+ test('GET /marker-icon/*', async () => {
38
+ // latest changes break local icon generation
39
+ const res = await injectWithHeaders({
40
+ method: 'GET',
41
+ // url: `/api/marker-icon/pin6-m-fa-space-shuttle+teal.png`,
42
+ url: '/api/marker-icon/icon-l-fa-location-dot+ff0000.svg',
43
+ headers: { uid: user.id, user_type: user.type }
44
+ });
45
+ if (res.statusCode === 200) {
46
+ expect(res.statusCode).toBe(200);
47
+ expect(res.body.includes('PNG')).toBeTruthy();
48
+ expect(res.rawPayload?.length).toBeTruthy();
49
+ } else {
50
+ expect(res.statusCode).toBe(404);
51
+ expect(res.body.message).toMatch('icon not found');
52
+ }
53
+ }, 15000);
54
+
55
+ test('GET /gis-icon/*', async () => {
56
+ const res = await injectWithHeaders({
57
+ method: 'GET',
58
+ url: `/api/gis-icon/pin6-m-fa-plus-square+7ed957.png`,
59
+ headers: { uid: user.id, user_type: user.type }
60
+ });
61
+ expect(res.statusCode).toBe(200);
62
+ expect(res.headers['content-type']).toBe('image/png');
63
+ expect(res.body.includes('PNG')).toBeTruthy();
64
+ });
65
+
66
+ test('GET /icon/*', async () => {
67
+ const res = await injectWithHeaders({
68
+ method: 'GET',
69
+ url: `/api/icon/pin6-m-fa-plus-square+7ed957.png`,
70
+ headers: { uid: user.id, user_type: user.type }
71
+ });
72
+ expect(res.statusCode).toBe(200);
73
+ expect(res.headers['content-type']).toBe('image/png');
74
+ expect(res.body.includes('PNG')).toBeTruthy();
75
+ });
76
+
77
+ test('GET /gis-icon/*.svg', async () => {
78
+ const res = await injectWithHeaders({
79
+ method: 'GET',
80
+ url: `/api/gis-icon/pin6-m-fa-plus-square+7ed957.svg`,
81
+ headers: { uid: user.id, user_type: user.type }
82
+ });
83
+ expect(res.statusCode).toBe(200);
84
+ expect(res.headers['content-type']).toBe('image/svg+xml');
85
+ expect(res.body).toContain('<svg');
86
+ expect(res.body).toContain('viewBox="0 0 446 518"');
87
+ });
88
+
89
+ test('GET /gis-icon/ar-* renders a symbol', async () => {
90
+ const res = await injectWithHeaders({
91
+ method: 'GET',
92
+ url: `/api/gis-icon/pin6-m-ar-E+ff4500.svg`,
93
+ headers: { uid: user.id, user_type: user.type }
94
+ });
95
+ expect(res.statusCode).toBe(200);
96
+ expect(res.headers['content-type']).toBe('image/svg+xml');
97
+ expect(res.body).toContain('>E</text>');
98
+ expect(res.body).toContain('fill="#ff4500"');
99
+ expect(res.body).not.toContain('#ff0000');
100
+ });
101
+
102
+ test.each(['pin2', 'pin3', 'pin4', 'pin5', 'pin6', 'pin7'])(
103
+ 'GET /gis-icon/%s recolors the background',
104
+ async (pin) => {
105
+ const res = await injectWithHeaders({
106
+ method: 'GET',
107
+ url: `/api/gis-icon/${pin}-m-fa-plus-square+7ed957.svg`,
108
+ headers: { uid: user.id, user_type: user.type }
109
+ });
110
+ expect(res.statusCode).toBe(200);
111
+ expect(res.body).not.toContain('#ff0000');
112
+ expect(res.body).toContain('fill="#7ed957"');
113
+ expect(res.body.lastIndexOf('data-gis-icon-layer="foreground"'))
114
+ .toBeGreaterThan(res.body.lastIndexOf('<use'));
115
+ },
116
+ );
117
+
118
+ test.each([
119
+ ['pin', '#ff0000'],
120
+ ['pin1', '#ff0000'],
121
+ ['circle1', '#176e1c'],
122
+ ['circle2', '#ff0000'],
123
+ ['none', 'fill="red"'],
124
+ ])(
125
+ 'GET /gis-icon/%s keeps the original background',
126
+ async (pin, backgroundColor) => {
127
+ const res = await injectWithHeaders({
128
+ method: 'GET',
129
+ url: `/api/gis-icon/${pin}-m-fa-plus-square+7ed957.svg`,
130
+ headers: { uid: user.id, user_type: user.type }
131
+ });
132
+ expect(res.statusCode).toBe(200);
133
+ expect(res.body).toContain(backgroundColor);
134
+ expect(res.body).toContain('fill="#7ed957"');
135
+ expect(res.body.lastIndexOf('data-gis-icon-layer="foreground"'))
136
+ .toBeGreaterThan(res.body.lastIndexOf('<use'));
137
+ },
138
+ );
139
+
140
+ test('GET /maps', async () => {
141
+ const res = await injectWithHeaders({
142
+ method: 'GET', url: `/api/maps`,
143
+ headers: { uid: user.id, user_type: user.type }
144
+ });
145
+ expect(res.statusCode).toBe(200);
146
+ const rep = res.json();
147
+ expect(rep).toBeInstanceOf(Array);
148
+ if (rep?.length) {
149
+ expect(rep?.[0]?.name).toBeTruthy();
150
+ expect(rep?.[0]?.slug).toBeTruthy();
151
+ }
152
+ });
153
+
154
+ test(`GET /map/:name`, async () => {
155
+ const slug = 'addr';
156
+ const body = await getTemplate('map', slug);
157
+ expect(body).toBeTruthy();
158
+
159
+ const res = await injectWithHeaders({
160
+ method: 'GET', url: `/api/map/${slug}`,
161
+ headers: { uid: user.id, user_type: user.type }
162
+ });
163
+ expect(res.statusCode).toBe(200);
164
+ // expect(res.body).toHaveProperty('name');
165
+ // expect(res.body.name).toBe('Адресний реєстр');
166
+ expect(res.body).toHaveProperty('source_path');
167
+ expect(res.body.source_path).toBe('data_address.street');
168
+ expect(res.body).toHaveProperty('layers');
169
+ expect(res.body.layers.length).toBe(3);
170
+ expect(res.body).toHaveProperty('maps');
171
+ expect(res.body.maps.length).toBeGreaterThan(0);
172
+ });
173
+
174
+ test(`GET /map-features/:slug/:id`, async () => {
175
+ const slug = 'addr';
176
+ const res = await injectWithHeaders({
177
+ method: 'GET',
178
+ url: `/api/map-features/${slug}/${addr.addr_id}`,
179
+ });
180
+ expect(res.statusCode).toBe(200);
181
+ const rep = res.json();
182
+ expect(rep.data).toBeTruthy();
183
+ expect(rep.meta).toBeTruthy();
184
+ expect(rep.data.addr_id).toBe(addr.addr_id);
185
+ });
186
+
187
+ test(`GET /map-features-point/:slug?x=&y=`, async () => {
188
+ const slug = 'addr';
189
+ const res = await injectWithHeaders({
190
+ method: 'GET',
191
+ url: `/api/map-features-point/${slug}?x=${addr.st_x}&y=${addr.st_y}`,
192
+ headers: { uid: user.id, user_type: user.type }
193
+ });
194
+ expect(res.statusCode).toBe(200);
195
+ const rep = res.json();
196
+ expect(rep.data).toBeTruthy();
197
+ expect(rep.meta).toBeTruthy();
198
+ expect(rep.data.addr_id).toBe(addr.addr_id);
199
+ });
200
+
201
+ test(`GET /map-tiles/:slug/:z/:y/:x.pbf`, async () => {
202
+ const slug = 'addr';
203
+ const res = await injectWithHeaders({
204
+ method: 'GET',
205
+ url: `/api/map-tiles/${slug}/14/9767/5659.pbf`,
206
+ headers: { uid: user.id, user_type: user.type }
207
+ });
208
+ expect(res.statusCode).toBe(200);
209
+ expect(res.headers['content-type']).toBe('application/x-protobuf');
210
+ expect(res.body.length).toBeGreaterThan(0);
211
+ expect(res.body).toMatch('\u001a�8\n\u0006street');
212
+ });
213
+
214
+ test(`GET /api/map-catalog/:service/:attr`, async () => {
215
+ const { id, columns } = await pg.query('select service_id as id, coalesce(filters::jsonb,attributes::jsonb,card::jsonb,popup::jsonb) as columns from gis.services where source_path is not null and is_active and is_public limit 1').then(r => r.rows?.[0] || {});
216
+ expect(id).toBeTypeOf('string');
217
+ expect(columns).toBeInstanceOf(Array);
218
+ expect(columns.length).toBeGreaterThan(0);
219
+ const { name: attr, data } = columns.find(col => col.data) || {};
220
+ expect(attr).toBeTypeOf('string');
221
+ expect(data).toBeTypeOf('string');
222
+ const res = await injectWithHeaders({
223
+ method: 'GET',
224
+ url: `/api/map-catalog/${id}/${attr}`,
225
+ headers: { uid: user.id, user_type: user.type }
226
+ });
227
+ expect(res.statusCode).toBe(200);
228
+ expect(res.body).toHaveProperty('legend');
229
+ expect(res.body.legend).toBeInstanceOf(Array);
230
+ if (res.body.legend.length) {
231
+ expect(res.body.legend[0]).toHaveProperty('id');
232
+ expect(res.body.legend[0]).toHaveProperty('count');
233
+ expect(res.body.legend[0].count).toMatch(/\d+/);
234
+ expect(+res.body.legend[0].count).toBeGreaterThanOrEqual(0);
235
+ }
236
+ expect(res.body).toHaveProperty('min');
237
+ expect(res.body).toHaveProperty('max');
238
+ });
239
+
240
+ test(`GET /api/map-catalog/:service/:attr?cls=`, async () => {
241
+ const { id, columns } = await pg.query('select service_id as id, coalesce(filters::jsonb,attributes::jsonb,card::jsonb,popup::jsonb) as columns from gis.services where source_path is not null and is_active and is_public limit 1').then(r => r.rows?.[0] || {});
242
+ expect(id).toBeTypeOf('string');
243
+ expect(columns).toBeInstanceOf(Array);
244
+ expect(columns.length).toBeGreaterThan(0);
245
+ const { name: attr, data } = columns.find(col => col.data) || {};
246
+ expect(attr).toBeTypeOf('string');
247
+ expect(data).toBeTypeOf('string');
248
+ const res = await injectWithHeaders({
249
+ method: 'GET',
250
+ url: `/api/map-catalog/${id}/${attr}`,
251
+ query: { cls: data },
252
+ headers: { uid: user.id, user_type: user.type }
253
+ });
254
+ expect(res.statusCode).toBe(200);
255
+ expect(res.body).toHaveProperty('legend');
256
+ expect(res.body.legend).toBeInstanceOf(Array);
257
+ if (res.body.legend.length) {
258
+ expect(res.body.legend[0]).toHaveProperty('id');
259
+ expect(res.body.legend[0]).toHaveProperty('count');
260
+ expect(res.body.legend[0].count).toMatch(/\d+/);
261
+ expect(+res.body.legend[0].count).toBeGreaterThanOrEqual(0);
262
+ expect(res.body.legend[0]).toHaveProperty('text');
263
+ expect(res.body.legend[0].text).toBeTypeOf('string');
264
+ }
265
+ expect(res.body).toHaveProperty('min');
266
+ expect(res.body).toHaveProperty('max');
267
+ });
268
+
269
+ test(`GET /api/vtile/:layer/:lang/:z/:y/:x`, async () => {
270
+ const layer = await pg.query('select service_id from gis.services where source_path is not null and is_active and is_public limit 1').then(r => r.rows?.[0]?.service_id);
271
+ expect(layer).toBeTypeOf('string');
272
+ const res = await injectWithHeaders({
273
+ method: 'GET',
274
+ url: `/api/vtile/${layer}/ua/1/1/1.pbf`,
275
+ // headers: { uid: user.id, user_type: user.type }
276
+ });
277
+ expect(res.statusCode).toBe(200);
278
+ expect(res.headers).toHaveProperty('cache-control');
279
+ expect(res.headers).toHaveProperty('content-type');
280
+ expect(res.headers['content-type']).toBe("application/x-protobuf");
281
+ expect(res.headers).toHaveProperty('content-length');
282
+ });
283
+
284
+ test(`GET /api/gis-clear-vtile/:id`, async () => {
285
+ const layer = await pg.query('select service_id from gis.services where source_path is not null and is_active and is_public limit 1').then(r => r.rows?.[0]?.service_id);
286
+ expect(layer).toBeTypeOf('string');
287
+ const res = await injectWithHeaders({
288
+ method: 'GET',
289
+ url: `/api/gis-clear-vtile/${layer}`,
290
+ headers: { uid: user.id, user_type: user.type }
291
+ });
292
+ expect(res.statusCode).toBe(200);
293
+ expect(res.body).toHaveProperty('message');
294
+ expect(res.body.message).toBe('Тайли очищено');
295
+ });
296
+ test(`GET /api/geojson/:layer`, async () => {
297
+ const layer = await pg.query('select service_id from gis.services where source_path is not null and is_active and is_public limit 1').then(r => r.rows?.[0]?.service_id);
298
+ expect(layer).toBeTypeOf('string');
299
+ const res = await injectWithHeaders({
300
+ method: 'GET',
301
+ url: `/api/geojson/${layer}`,
302
+ // headers: { uid: user.id, user_type: user.type }
303
+ });
304
+ expect(res.statusCode).toBe(200);
305
+ expect(res.body).toHaveProperty('type');
306
+ expect(res.body.type).toBe('FeatureCollection');
307
+ expect(res.body).toHaveProperty('features');
308
+ expect(res.body.features).toBeInstanceOf(Array);
309
+ expect(res.body.features.length).toBeGreaterThan(0);
310
+ expect(res.body.features[0]).toHaveProperty('type');
311
+ expect(res.body.features[0].type).toBe('Feature');
312
+ expect(res.body.features[0]).toHaveProperty('geometry');
313
+ expect(res.body.features[0].geometry).toHaveProperty('type');
314
+ expect(res.body.features[0].geometry.type).toBeOneOf(['Point', 'LineString', 'Polygon']);
315
+ expect(res.body.features[0].geometry).toHaveProperty('coordinates');
316
+ expect(res.body.features[0].geometry.coordinates).toBeInstanceOf(Array);
317
+ expect(res.body.features[0]).toHaveProperty('properties');
318
+ expect(Object.keys(res.body.features[0].properties).length).toBeGreaterThan(0);
319
+ });
320
+ test(`GET /api/json/:layer`, async () => {
321
+ const layer = await pg.query('select register_id from gis.registers where 1=1 and is_active and is_public limit 1').then(r => r.rows?.[0]?.register_id);
322
+ expect(layer).toBeTypeOf('string');
323
+ const res = await injectWithHeaders({
324
+ method: 'GET',
325
+ url: `/api/json/${layer}`,
326
+ // headers: { uid: user.id, user_type: user.type }
327
+ });
328
+ expect(res.statusCode).toBe(200);
329
+ expect(res.body).toHaveProperty('register_id');
330
+ expect(res.body).toHaveProperty('total');
331
+ expect(res.body).toHaveProperty('limit');
332
+ expect(res.body).toHaveProperty('page');
333
+ expect(res.body).toHaveProperty('data');
334
+ expect(res.body.data).toBeInstanceOf(Array);
335
+ expect(res.body.data.length).toBeGreaterThan(0);
336
+ expect(res.body.data[0]).toHaveProperty('id');
337
+ });
338
+ test(`GET /api/gis-layer-list`, async () => {
339
+ const res = await injectWithHeaders({
340
+ method: 'GET',
341
+ url: `/api/gis-layer-list`,
342
+ // headers: { uid: user.id, user_type: user.type }
343
+ });
344
+ expect(res.statusCode).toBe(200);
345
+ });
346
+
347
+ test(`GET /api/map-format?layer=&id=`, async () => {
348
+ const { service_id: layer, source_path: table } = await pg.query('select service_id, source_path from gis.services where source_path is not null and is_active and is_public limit 1').then(r => r.rows?.[0] || {});
349
+ expect(layer).toBeTypeOf('string');
350
+ expect(pg.pk?.[table]).toBeTypeOf('string');
351
+
352
+ const id = await pg.query(`select ${pg.pk?.[table]} as id from ${table} limit 1`).then(r => r.rows?.[0]?.id);
353
+ expect(id).toBeTypeOf('string');
354
+
355
+ const res = await injectWithHeaders({
356
+ method: 'GET',
357
+ url: `/api/map-format`,
358
+ query: {
359
+ layer,
360
+ id
361
+ },
362
+ headers: { uid: user.id, user_type: user.type }
363
+ });
364
+ expect(res.statusCode).toBe(200);
365
+ expect(res.body).toHaveProperty('id');
366
+ expect(res.body).toHaveProperty('data');
367
+ expect(res.body).toHaveProperty('columns');
368
+ expect(res.body.columns).toBeInstanceOf(Array);
369
+ expect(res.body.data).toHaveProperty('id');
370
+ expect(res.body.data).toHaveProperty('geom');
371
+ });
372
+ test(`GET /api/map-format?layer=&lng=&lat=`, async () => {
373
+ const { cartocss_id: layer, card_table: table } = await pg.query('select cartocss_id, card_table from gis.cartocss where card_table is not null and enabled and is_public limit 1').then(r => r.rows?.[0] || {});
374
+ expect(layer).toBeTypeOf('string');
375
+ expect(pg.pk?.[table]).toBeTypeOf('string');
376
+
377
+ const { st_x: lng, st_y: lat } = await pg.query(`select st_x(st_pointonsurface(st_transform(geom,4326))), st_y(st_pointonsurface(st_transform(geom,4326))) from ${table} where geom is not null limit 1`).then(r => r.rows[0]);
378
+ expect(lat).toBeTypeOf('number');
379
+ const res = await injectWithHeaders({
380
+ method: 'GET',
381
+ url: `/api/map-format`,
382
+ query: {
383
+ layer,
384
+ lng,
385
+ lat
386
+ },
387
+ headers: { uid: user.id, user_type: user.type }
388
+ });
389
+ expect(res.statusCode).toBe(200);
390
+ expect(res.body).toHaveProperty('id');
391
+ expect(res.body).toHaveProperty('ids');
392
+ expect(res.body.ids).toBeInstanceOf(Array);
393
+ expect(res.body).toHaveProperty('data');
394
+ expect(res.body).toHaveProperty('columns');
395
+ expect(res.body.columns).toBeInstanceOf(Array);
396
+ expect(res.body.data).toHaveProperty('id');
397
+ expect(res.body.data).toHaveProperty('geom');
398
+ expect(res.body.data).toHaveProperty('distance'); // self-explanatory
399
+ expect(res.body.data).toHaveProperty('key'); // cartocss layer key
400
+ });
401
+ test(`GET /api/coords/height?lat=&lng=`, async () => {
402
+ const res = await injectWithHeaders({
403
+ method: 'GET',
404
+ url: `/api/coords/height`,
405
+ query: { lat: 50, lng: 30 },
406
+ headers: { uid: user.id, user_type: user.type }
407
+ });
408
+ expect(res.statusCode).toBe(200);
409
+ expect(res.body).toHaveProperty('height');
410
+ expect(res.body.height).toBeTypeOf('number');
411
+ expect(res.body.height).toBe(201);
412
+ expect(res.body).toHaveProperty('lat');
413
+ expect(res.body).toHaveProperty('lng');
414
+ });
415
+ test(`POST /api/gis-parse-geo`, async () => {
416
+ const res = await injectWithHeaders({
417
+ method: 'POST',
418
+ url: `/api/gis-parse-geo`,
419
+ payload: {
420
+ content: `{ "type":"Feature", "geometry": { "type": "Point", "coordinates": [30.523,50.4501] }, "properties": {} }`
421
+ },
422
+ headers: { uid: user.id, user_type: user.type }
423
+ });
424
+ expect(res.statusCode).toBe(200);
425
+ expect(res.body).toHaveProperty('type');
426
+ expect(res.body.type).toBe('FeatureCollection');
427
+ expect(res.body).toHaveProperty('features');
428
+ expect(res.body.features).toBeInstanceOf(Array);
429
+ expect(res.body.features.length).toBeGreaterThan(0);
430
+ expect(res.body.features[0]).toHaveProperty('type');
431
+ expect(res.body.features[0].type).toBe('Feature');
432
+ expect(res.body.features[0]).toHaveProperty('geometry');
433
+ expect(res.body.features[0].geometry).toHaveProperty('type');
434
+ expect(res.body.features[0].geometry.type).toBeOneOf(['Point', 'LineString', 'Polygon']);
435
+ expect(res.body.features[0].geometry).toHaveProperty('coordinates');
436
+ expect(res.body.features[0].geometry.coordinates).toBeInstanceOf(Array);
437
+ expect(res.body.features[0]).toHaveProperty('properties');
438
+ });
439
+ test(`POST /api/gis-convert (file as base64 + filename)`, async () => {
440
+ const res = await injectWithHeaders({
441
+ method: 'POST',
442
+ url: `/api/gis-convert`,
443
+ payload: {
444
+ file: base64,
445
+ filename: 'shapefile.zip',
446
+ },
447
+ headers: { uid: user.id, user_type: user.type }
448
+ });
449
+ expect(res.statusCode).toBe(200);
450
+ expect(res.body).toHaveProperty('type');
451
+ expect(res.body.type).toBe('FeatureCollection');
452
+ expect(res.body).toHaveProperty('features');
453
+ expect(res.body.features).toBeInstanceOf(Array);
454
+ expect(res.body.features.length).toBeGreaterThan(0);
455
+ expect(res.body.features[0]).toHaveProperty('type');
456
+ expect(res.body.features[0].type).toBe('Feature');
457
+ expect(res.body.features[0]).toHaveProperty('geometry');
458
+ expect(res.body.features[0].geometry).toHaveProperty('type');
459
+ expect(res.body.features[0].geometry.type).toBe('Point');
460
+ expect(res.body.features[0].geometry).toHaveProperty('coordinates');
461
+ expect(res.body.features[0].geometry.coordinates).toBeInstanceOf(Array);
462
+ expect(res.body.features[0].geometry.coordinates[0]).toBe(30.5234);
463
+ expect(res.body.features[0].geometry.coordinates[1]).toBe(50.4501);
464
+ expect(res.body.features[0]).toHaveProperty('properties');
465
+ expect(res.body.features[0].properties).toHaveProperty('id');
466
+ expect(res.body.features[0].properties.id).toBe(1);
467
+ });
468
+
469
+ test(`POST /api/gis-convert (filepath)`, async () => {
470
+ const relpath = '/files/test/shapefile.zip';
471
+ const exists = await isFileExists(relpath);
472
+ if (!exists) {
473
+ await uploadFile(relpath, Buffer.from(base64, 'base64'));
474
+ }
475
+
476
+ const res = await injectWithHeaders({
477
+ method: 'POST',
478
+ url: `/api/gis-convert`,
479
+ payload: {
480
+ filepath: relpath
481
+ },
482
+ headers: { uid: user.id, user_type: user.type }
483
+ });
484
+ expect(res.statusCode).toBe(200);
485
+ expect(res.body).toHaveProperty('type');
486
+ expect(res.body.type).toBe('FeatureCollection');
487
+ expect(res.body).toHaveProperty('features');
488
+ expect(res.body.features).toBeInstanceOf(Array);
489
+ expect(res.body.features.length).toBeGreaterThan(0);
490
+ expect(res.body.features[0]).toHaveProperty('type');
491
+ expect(res.body.features[0].type).toBe('Feature');
492
+ expect(res.body.features[0]).toHaveProperty('geometry');
493
+ expect(res.body.features[0].geometry).toHaveProperty('type');
494
+ expect(res.body.features[0].geometry.type).toBe('Point');
495
+ expect(res.body.features[0].geometry).toHaveProperty('coordinates');
496
+ expect(res.body.features[0].geometry.coordinates).toBeInstanceOf(Array);
497
+ expect(res.body.features[0].geometry.coordinates[0]).toBe(30.5234);
498
+ expect(res.body.features[0].geometry.coordinates[1]).toBe(50.4501);
499
+ expect(res.body.features[0]).toHaveProperty('properties');
500
+ expect(res.body.features[0].properties).toHaveProperty('id');
501
+ expect(res.body.features[0].properties.id).toBe(1);
502
+ });
503
+ });