@kalisio/kdk 2.1.9 → 2.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 (243) hide show
  1. package/.travis.test.sh +42 -10
  2. package/README.md +2 -2
  3. package/core/api/application.js +6 -1
  4. package/core/api/authentication.js +17 -1
  5. package/core/api/db.js +7 -2
  6. package/core/api/hooks/hooks.authentication.js +4 -2
  7. package/core/api/hooks/hooks.authorisations.js +12 -2
  8. package/core/api/hooks/hooks.model.js +5 -5
  9. package/core/api/hooks/hooks.organisations.js +0 -4
  10. package/core/api/services/account/account.hooks.js +10 -6
  11. package/core/api/services/account/account.service.js +1 -1
  12. package/{map/api/services/geocoder/geocoder.hooks.js → core/api/services/import-export/import-export.hooks.js} +7 -5
  13. package/core/api/services/import-export/import-export.service.js +11 -0
  14. package/core/api/services/index.js +13 -1
  15. package/core/api/services/users/users.hooks.js +2 -3
  16. package/core/client/api.js +16 -14
  17. package/core/client/capabilities.js +6 -2
  18. package/core/client/components/KContent.vue +11 -1
  19. package/core/client/components/KDialog.vue +17 -15
  20. package/core/client/components/KSponsor.vue +1 -1
  21. package/core/client/components/KTextArea.vue +5 -1
  22. package/core/client/components/app/KAbout.vue +1 -2
  23. package/core/client/components/app/KWelcome.vue +3 -5
  24. package/core/client/components/chart/KTimeSeriesChart.vue +24 -37
  25. package/core/client/components/collection/KColumn.vue +20 -17
  26. package/core/client/components/editor/KModalEditor.vue +0 -2
  27. package/core/client/components/form/KChipsField.vue +12 -2
  28. package/core/client/components/form/KColorField.vue +12 -2
  29. package/core/client/components/form/KColorScaleField.vue +12 -2
  30. package/core/client/components/form/KDateTimeRangeField.vue +12 -2
  31. package/core/client/components/form/KDatetimeField.vue +12 -2
  32. package/core/client/components/form/KEmailField.vue +12 -2
  33. package/core/client/components/form/KFileField.vue +12 -2
  34. package/core/client/components/form/KForm.vue +43 -9
  35. package/core/client/components/form/KIconField.vue +12 -2
  36. package/core/client/components/form/KItemField.vue +25 -4
  37. package/core/client/components/form/KNumberField.vue +12 -2
  38. package/core/client/components/form/KOptionsField.vue +12 -2
  39. package/core/client/components/form/KPasswordField.vue +12 -2
  40. package/core/client/components/form/KPhoneField.vue +13 -3
  41. package/core/client/components/form/KPropertyItemField.vue +12 -2
  42. package/core/client/components/form/KResolutionField.vue +126 -0
  43. package/core/client/components/form/KRoleField.vue +12 -2
  44. package/core/client/components/form/KSelectField.vue +14 -4
  45. package/core/client/components/form/KTextField.vue +12 -2
  46. package/core/client/components/form/KTextareaField.vue +13 -3
  47. package/core/client/components/form/KToggleField.vue +12 -2
  48. package/core/client/components/form/KTokenField.vue +12 -2
  49. package/core/client/components/form/KUnitField.vue +12 -2
  50. package/core/client/components/form/KUrlField.vue +12 -2
  51. package/core/client/components/input/KIconChooser.vue +10 -12
  52. package/core/client/components/input/KPalette.vue +2 -1
  53. package/core/client/components/layout/KPage.vue +5 -4
  54. package/core/client/components/layout/KWindow.vue +10 -10
  55. package/core/client/components/media/KColorScale.vue +26 -20
  56. package/core/client/components/media/KImageViewer.vue +57 -33
  57. package/core/client/components/media/KShape.vue +14 -103
  58. package/core/client/components/screen/KRegisterScreen.vue +0 -1
  59. package/core/client/components/screen/KScreenFooter.vue +0 -18
  60. package/core/client/components/team/KAddMember.vue +16 -22
  61. package/core/client/components/team/KGroupsActivity.vue +14 -0
  62. package/core/client/components/team/KMembersActivity.vue +12 -0
  63. package/core/client/components/team/KTagsActivity.vue +14 -0
  64. package/core/client/components/time/KDateTime.vue +23 -7
  65. package/core/client/components/time/KTimeControl.vue +142 -0
  66. package/core/client/components/tool/KExportTool.vue +57 -0
  67. package/core/client/composables/collection.js +0 -1
  68. package/core/client/composables/pwa.js +0 -1
  69. package/core/client/composables/schema.js +1 -1
  70. package/core/client/composables/session.js +30 -6
  71. package/core/client/exporter.js +141 -0
  72. package/core/client/i18n/core_en.json +91 -23
  73. package/core/client/i18n/core_fr.json +92 -23
  74. package/core/client/index.js +3 -0
  75. package/core/client/layout.js +34 -14
  76. package/core/client/local-storage.js +8 -6
  77. package/core/client/mixins/index.js +0 -1
  78. package/core/client/mixins/mixin.base-field.js +24 -2
  79. package/core/client/mixins/mixin.object-proxy.js +0 -1
  80. package/core/client/search.js +2 -1
  81. package/core/client/services/index.js +2 -1
  82. package/core/client/services/local-settings.service.js +4 -4
  83. package/core/client/theme.js +3 -3
  84. package/core/client/time.js +4 -0
  85. package/core/client/units.js +149 -4
  86. package/core/client/utils/index.js +13 -6
  87. package/core/client/utils/utils.account.js +1 -1
  88. package/core/client/utils/utils.colors.js +43 -0
  89. package/core/client/utils/utils.platform.js +0 -1
  90. package/core/client/utils/utils.pwa.js +14 -14
  91. package/core/client/utils/utils.session.js +1 -1
  92. package/core/client/utils/utils.shapes.js +270 -0
  93. package/core/client/utils/utils.time.js +37 -0
  94. package/core/common/permissions.js +3 -0
  95. package/core/common/schemas/settings.update.json +50 -29
  96. package/extras/css/core.variables.scss +3 -1
  97. package/extras/tours/map/navigation-bar.js +17 -15
  98. package/extras/tours/map/timeline.js +33 -33
  99. package/map/api/config/categories.cjs +4 -1
  100. package/map/api/hooks/hooks.catalog.js +39 -0
  101. package/map/api/hooks/hooks.features.js +23 -3
  102. package/map/api/hooks/hooks.query.js +31 -10
  103. package/map/api/models/projects.model.mongodb.js +8 -0
  104. package/map/api/services/catalog/catalog.hooks.js +5 -3
  105. package/map/api/services/features/features.hooks.js +18 -6
  106. package/map/api/services/index.js +22 -6
  107. package/map/api/services/projects/projects.hooks.js +118 -0
  108. package/map/client/capture.js +16 -0
  109. package/map/client/cesium/utils/index.js +3 -0
  110. package/map/client/cesium/utils/utils.events.js +30 -0
  111. package/map/client/cesium/utils/utils.popup.js +17 -0
  112. package/map/client/cesium/{utils.js → utils/utils.style.js} +53 -49
  113. package/map/client/components/KCapture.vue +50 -0
  114. package/map/client/components/KCaptureTextArea.vue +53 -0
  115. package/map/client/components/KCompass.vue +2 -2
  116. package/map/client/components/KFeaturesChart.vue +1 -1
  117. package/map/client/components/KFeaturesFilter.vue +2 -2
  118. package/map/client/components/KLayerStyleForm.vue +256 -430
  119. package/map/client/components/KLevelSlider.vue +1 -1
  120. package/map/client/components/KNorth.vue +31 -0
  121. package/map/client/components/KProjectMenu.vue +88 -0
  122. package/map/client/components/KTimezoneMap.vue +36 -23
  123. package/map/client/components/catalog/KAddLayer.vue +3 -4
  124. package/map/client/components/catalog/KConnectLayer.vue +16 -4
  125. package/map/client/components/catalog/KCreateLayer.vue +1 -2
  126. package/map/client/components/catalog/KCreateProject.vue +100 -0
  127. package/map/client/components/catalog/KCreateView.vue +25 -2
  128. package/map/client/components/catalog/KLayersPanel.vue +24 -27
  129. package/map/client/components/catalog/KLayersSelector.vue +1 -1
  130. package/map/client/components/catalog/KProjectEditor.vue +91 -0
  131. package/map/client/components/catalog/KProjectManager.vue +60 -0
  132. package/map/client/components/catalog/KProjectSelector.vue +38 -0
  133. package/map/client/components/catalog/KProjectsPanel.vue +153 -0
  134. package/map/client/components/catalog/KSelectLayers.vue +96 -0
  135. package/map/client/components/catalog/KSelectViews.vue +96 -0
  136. package/map/client/components/catalog/KViewsPanel.vue +66 -30
  137. package/map/client/components/form/KDirectionField.vue +24 -5
  138. package/map/client/components/form/KLayerCategoryField.vue +12 -2
  139. package/map/client/components/form/KLocationField.vue +20 -5
  140. package/map/client/components/form/KOwsLayerField.vue +12 -2
  141. package/map/client/components/form/KOwsServiceField.vue +12 -2
  142. package/map/client/components/form/KSelectLayersField.vue +159 -0
  143. package/map/client/components/form/KSelectViewsField.vue +121 -0
  144. package/map/client/components/form/KTimezoneField.vue +24 -17
  145. package/map/client/components/legend/KColorScaleLegend.vue +1 -1
  146. package/map/client/components/legend/KLayerLegend.vue +61 -0
  147. package/map/client/components/legend/KLegend.vue +45 -44
  148. package/map/client/components/legend/KLegendRenderer.vue +5 -3
  149. package/map/client/components/legend/KSymbolsLegend.vue +12 -10
  150. package/map/client/components/legend/KVariablesLegend.vue +78 -0
  151. package/map/client/components/location/KGeocodersFilter.vue +2 -4
  152. package/map/client/components/location/KLocationMap.vue +48 -17
  153. package/map/client/components/location/KLocationSearch.vue +13 -3
  154. package/map/client/components/tools/KSearchTool.vue +17 -12
  155. package/map/client/components/widget/KElevationProfile.vue +16 -19
  156. package/map/client/components/widget/KMapillaryViewer.vue +21 -22
  157. package/map/client/components/widget/KTimeSeries.vue +35 -23
  158. package/map/client/composables/activity.js +15 -2
  159. package/map/client/composables/catalog.js +66 -0
  160. package/map/client/composables/highlight.js +56 -20
  161. package/map/client/composables/index.js +2 -0
  162. package/map/client/composables/location.js +25 -18
  163. package/map/client/composables/project.js +122 -0
  164. package/map/client/geolocation.js +1 -1
  165. package/map/client/globe.js +2 -0
  166. package/map/client/i18n/map_en.json +123 -76
  167. package/map/client/i18n/map_fr.json +124 -72
  168. package/map/client/index.js +3 -0
  169. package/map/client/init.js +17 -0
  170. package/map/client/leaflet/GSMaPLayer.js +16 -17
  171. package/map/client/leaflet/ShapeMarker.js +40 -0
  172. package/map/client/leaflet/TiledFeatureLayer.js +1 -1
  173. package/map/client/leaflet/TiledMeshLayer.js +11 -15
  174. package/map/client/leaflet/TiledWindLayer.js +6 -10
  175. package/map/client/leaflet/utils/index.js +4 -0
  176. package/map/client/leaflet/utils/utils.events.js +41 -0
  177. package/map/client/leaflet/utils/utils.popup.js +21 -0
  178. package/map/client/leaflet/utils/utils.style.js +191 -0
  179. package/map/client/leaflet/utils/utils.tiles.js +87 -0
  180. package/map/client/map.js +2 -0
  181. package/map/client/mixins/globe/mixin.base-globe.js +29 -21
  182. package/map/client/mixins/globe/mixin.geojson-layers.js +132 -69
  183. package/map/client/mixins/globe/mixin.popup.js +2 -1
  184. package/map/client/mixins/globe/mixin.style.js +6 -4
  185. package/map/client/mixins/globe/mixin.tooltip.js +8 -3
  186. package/map/client/mixins/map/mixin.base-map.js +13 -11
  187. package/map/client/mixins/map/mixin.edit-layers.js +15 -15
  188. package/map/client/mixins/map/mixin.forecast-layers.js +3 -1
  189. package/map/client/mixins/map/mixin.geojson-layers.js +56 -20
  190. package/map/client/mixins/map/mixin.georaster-layers.js +4 -11
  191. package/map/client/mixins/map/mixin.heatmap-layers.js +1 -1
  192. package/map/client/mixins/map/mixin.popup.js +2 -1
  193. package/map/client/mixins/map/mixin.style.js +4 -67
  194. package/map/client/mixins/map/mixin.tiled-mesh-layers.js +2 -1
  195. package/map/client/mixins/map/mixin.tiled-wind-layers.js +4 -2
  196. package/map/client/mixins/map/mixin.tooltip.js +2 -1
  197. package/map/client/mixins/mixin.activity.js +66 -191
  198. package/map/client/mixins/mixin.catalog-panel.js +6 -6
  199. package/map/client/mixins/mixin.context.js +12 -9
  200. package/map/client/mixins/mixin.feature-service.js +29 -300
  201. package/map/client/mixins/mixin.weacast.js +11 -17
  202. package/map/client/pixi-utils.js +1 -1
  203. package/map/client/planets.js +58 -0
  204. package/map/client/utils/index.js +6 -0
  205. package/map/client/utils/utils.capture.js +176 -0
  206. package/map/client/utils/utils.catalog.js +149 -0
  207. package/map/client/utils/utils.features.js +364 -0
  208. package/map/client/utils/utils.js +0 -151
  209. package/map/client/utils/utils.layers.js +174 -0
  210. package/map/client/utils/utils.location.js +91 -23
  211. package/map/client/utils/utils.project.js +8 -0
  212. package/map/client/utils/utils.schema.js +0 -1
  213. package/map/client/utils/utils.style.js +297 -0
  214. package/map/client/utils.all.js +2 -2
  215. package/map/client/utils.globe.js +1 -1
  216. package/map/client/utils.map.js +1 -1
  217. package/map/common/permissions.js +2 -0
  218. package/map/common/schemas/capture.create.json +132 -0
  219. package/map/common/schemas/projects.create.json +52 -0
  220. package/map/common/schemas/projects.update.json +52 -0
  221. package/package.json +6 -5
  222. package/test/api/core/account.test.js +20 -0
  223. package/test/api/core/config/default.cjs +16 -3
  224. package/test/api/core/import-export.test.js +86 -0
  225. package/test/api/core/test-log-2024-01-04.log +14 -0
  226. package/test/api/map/catalog.test.js +164 -0
  227. package/test/api/map/index.test.js +25 -61
  228. package/test/api/map/test-log-2024-01-04.log +2 -0
  229. package/test/api/map/test-log-2024-01-11.log +1 -0
  230. package/test/api/map/test-log-2024-01-25.log +19 -0
  231. package/test/client/core/layout.js +25 -5
  232. package/test/client/core/utils.js +7 -0
  233. package/test/client/map/catalog.js +78 -1
  234. package/test/client/map/time.js +2 -1
  235. package/core/client/components/screen/KEndpointScreen.vue +0 -80
  236. package/core/client/mixins/mixin.account.js +0 -61
  237. package/extras/icons/kdk.png +0 -0
  238. package/map/api/services/geocoder/geocoder.service.js +0 -79
  239. package/map/client/components/KCaptureToolbar.vue +0 -155
  240. package/map/client/components/KColorLegend.vue +0 -349
  241. package/map/client/components/KTimeline.vue +0 -293
  242. package/map/client/components/KUrlLegend.vue +0 -122
  243. package/map/client/leaflet/utils.js +0 -246
@@ -1,78 +1,6 @@
1
1
  import _ from 'lodash'
2
- import rhumbBearing from '@turf/rhumb-bearing'
3
- import rhumbDistance from '@turf/rhumb-distance'
4
- import rotate from '@turf/transform-rotate'
5
- import scale from '@turf/transform-scale'
6
- import translate from '@turf/transform-translate'
7
2
  import chroma from 'chroma-js'
8
- import config from 'config'
9
3
  import formatcoords from 'formatcoords'
10
- import { buildUrl } from '../../../core/common/index.js'
11
- import { api, Store } from '../../../core/client/index.js'
12
-
13
- // Build a color map from a JS object specification
14
- export function buildColorMap (options) {
15
- let colorMap
16
- const classes = _.get(options, 'classes')
17
- const domain = _.get(options, 'domain')
18
- const scale = _.get(options, 'scale')
19
- const invert = _.get(options, 'invertScale')
20
- if (scale) {
21
- if (classes) {
22
- colorMap = chroma.scale(scale).classes(invert ? classes.slice().reverse() : classes)
23
- } else if (domain) {
24
- colorMap = chroma.scale(scale).domain(invert ? domain.slice().reverse() : domain)
25
- }
26
- }
27
- return colorMap
28
- }
29
-
30
- export function transformFeatures (features, transform) {
31
- features.forEach(feature => {
32
- const scaling = _.get(transform, 'scale')
33
- const rotation = _.get(transform, 'rotate')
34
- const translation = _.get(transform, 'translate')
35
- if (scaling) {
36
- scale(feature, scaling.factor,
37
- Object.assign(_.omit(scaling, ['factor']), { mutate: true }))
38
- }
39
- if (rotation) {
40
- rotate(feature, rotation.angle,
41
- Object.assign(_.omit(rotation, ['angle']), { mutate: true }))
42
- }
43
- if (translation) {
44
- // Could be expressed as direction/distance or target point
45
- // Take care that turfjs uses a rhumb line
46
- if (translation.point) {
47
- translation.distance = rhumbDistance(translation.pivot || [0, 0], translation.point)
48
- translation.direction = rhumbBearing(translation.pivot || [0, 0], translation.point)
49
- delete translation.pivot
50
- delete translation.point
51
- }
52
- translate(feature, translation.distance, translation.direction,
53
- Object.assign(_.omit(translation, ['direction', 'distance']), { mutate: true }))
54
- }
55
- })
56
- }
57
-
58
- export async function fetchGeoJson (dataSource, options = {}) {
59
- const response = await fetch(dataSource)
60
- if (response.status !== 200) {
61
- throw new Error(`Impossible to fetch ${dataSource}: ` + response.status)
62
- }
63
- const data = await response.json()
64
- const features = (data.type === 'FeatureCollection' ? data.features : [data])
65
- if (typeof options.processor === 'function') {
66
- features.forEach(feature => options.processor(feature))
67
- } else if (typeof options.processor === 'string') {
68
- const compiler = _.template(options.processor)
69
- features.forEach(feature => compiler({ feature, properties: feature.properties }))
70
- }
71
- if (options.transform) {
72
- transformFeatures(features, options.transform)
73
- }
74
- return data
75
- }
76
4
 
77
5
  // Find the nearest time of a given one in a given moment time list
78
6
  export function getNearestTime (time, times) {
@@ -106,72 +34,6 @@ export function getTimeInterval (times, mode = 'minimum') {
106
34
  return interval
107
35
  }
108
36
 
109
- // Format (reverse) geocoding output
110
- export function formatGeocodingResult (element) {
111
- let label = element.formattedAddress || ''
112
- if (!label) {
113
- if (element.streetNumber) label += (element.streetNumber + ', ')
114
- if (element.streetName) label += (element.streetName + ' ')
115
- if (element.city) label += (element.city + ' ')
116
- if (element.zipcode) label += (' (' + element.zipcode + ')')
117
- }
118
- return label
119
- }
120
-
121
- // Helper to set a JWT as query param in a target URL
122
- export function setUrlJwt (item, path, baseUrl, jwtField, jwt) {
123
- const url = _.get(item, path)
124
- if (!url) return
125
- // Check it conforms to required base URL
126
- if (!url.startsWith(baseUrl)) return
127
- // FIXME: specific case of Cesium OpenStreetMap provider
128
- // Because Cesium generates the final url as base url + tile scheme + extension
129
- // updating the base url property breaks it, for now we modify the extension
130
- if ((path === 'cesium.url') && _.get(item, 'cesium.type') === 'OpenStreetMap') {
131
- const ext = _.get(item, 'cesium.fileExtension', 'png')
132
- _.set(item, 'cesium.fileExtension', ext + `?${jwtField}=${jwt}`)
133
- } else {
134
- _.set(item, path, buildUrl(url, { [jwtField]: jwt }))
135
- }
136
- }
137
-
138
- // Helper to set required JWT as query param in a given set of layers for underlying engine
139
- export async function setEngineJwt (layers) {
140
- // If we need to use API gateway forward token as query parameter
141
- // (Leaflet does not support anything else by default as it mainly uses raw <img> tags)
142
- let jwt = (config.gatewayJwt ? await api.get('storage').getItem(config.gatewayJwt) : null)
143
- let jwtField = config.gatewayJwtField
144
- // Check both the default built-in config or the server provided one if any (eg mobile apps)
145
- const gatewayUrl = Store.get('capabilities.api.gateway', config.gateway)
146
- if (jwt) {
147
- layers.forEach(layer => {
148
- setUrlJwt(layer, 'iconUrl', gatewayUrl, jwtField, jwt)
149
- setUrlJwt(layer, 'leaflet.source', gatewayUrl, jwtField, jwt)
150
- setUrlJwt(layer, 'opendap.url', gatewayUrl, jwtField, jwt)
151
- setUrlJwt(layer, 'geotiff.url', gatewayUrl, jwtField, jwt)
152
- setUrlJwt(layer, 'wfs.url', gatewayUrl, jwtField, jwt)
153
- setUrlJwt(layer, 'wcs.url', gatewayUrl, jwtField, jwt)
154
- setUrlJwt(layer, 'cesium.url', gatewayUrl, jwtField, jwt)
155
- setUrlJwt(layer, 'cesium.source', gatewayUrl, jwtField, jwt)
156
- })
157
- }
158
- // We might also proxy some data directly from the app when using object storage
159
- // This is only for raw raster data not OGC protocols
160
- jwt = (config.apiJwt ? await api.get('storage').getItem(config.apiJwt) : null)
161
- jwtField = 'jwt'
162
- const apiUrl = api.getBaseUrl()
163
- if (jwt) {
164
- layers.forEach(layer => {
165
- setUrlJwt(layer, 'geotiff.url', apiUrl, jwtField, jwt)
166
- })
167
- // We also allow absolute URLs for app like /api/storage/xxx
168
- layers.forEach(layer => {
169
- setUrlJwt(layer, 'geotiff.url', '/', jwtField, jwt)
170
- })
171
- }
172
- return layers
173
- }
174
-
175
37
  export function getFeatureId (feature, layer) {
176
38
  let featureId = _.get(layer, 'featureId')
177
39
  // We need at least an internal ID to uniquely identify features for updates
@@ -213,16 +75,3 @@ export function coordinatesToGeoJSON (lat, lon, format, options) {
213
75
  }
214
76
  }
215
77
  }
216
-
217
- export function parseCoordinates (str) {
218
- const coords = _.split(_.trim(str), ',')
219
- if (coords.length !== 2) return
220
- const latitude = Number(coords[0])
221
- if (_.isNaN(latitude)) return
222
- const longitude = Number(coords[1])
223
- if (_.isNaN(longitude)) return
224
- return {
225
- latitude,
226
- longitude
227
- }
228
- }
@@ -0,0 +1,174 @@
1
+ import _ from 'lodash'
2
+ import logger from 'loglevel'
3
+ import { Notify, Loading } from 'quasar'
4
+ import explode from '@turf/explode'
5
+ import { i18n, api, utils as kCoreUtils } from '../../../core/client/index.js'
6
+ import { checkFeatures, createFeatures, removeFeatures } from './utils.features.js'
7
+
8
+ export const InternalLayerProperties = ['actions', 'label', 'isVisible', 'isDisabled']
9
+
10
+ export function isInMemoryLayer (layer) {
11
+ return layer._id === undefined
12
+ }
13
+
14
+ export function isUserLayer (layer) {
15
+ return (_.get(layer, 'scope') === 'user')
16
+ }
17
+
18
+ export function isFeatureLayer (layer) {
19
+ return (_.get(layer, 'service') === 'features')
20
+ }
21
+
22
+ export function hasFeatureSchema (layer) {
23
+ return _.has(layer, 'schema')
24
+ }
25
+
26
+ export function isLayerSelectable (layer) {
27
+ return _.get(layer, 'isSelectable', true)
28
+ }
29
+
30
+ export function isLayerProbable (layer) {
31
+ return _.get(layer, 'isProbable', false)
32
+ }
33
+
34
+ export function isLayerStorable (layer) {
35
+ // Only possible when not saved by default
36
+ if (layer._id) return false
37
+ return _.get(layer, 'isStorable', isUserLayer(layer))
38
+ }
39
+
40
+ export function isLayerEditable (layer) {
41
+ // Only possible when saved by default
42
+ if (!layer._id) return false
43
+ return _.get(layer, 'isEditable', isUserLayer(layer))
44
+ }
45
+
46
+ export function isLayerRemovable (layer) {
47
+ return _.get(layer, 'isRemovable', isUserLayer(layer))
48
+ }
49
+
50
+ export function isLayerStyleEditable (layer) {
51
+ return _.get(layer, 'isStyleEditable', isUserLayer(layer))
52
+ }
53
+
54
+ export function isLayerDataEditable (layer) {
55
+ return _.get(layer, 'isDataEditable', isUserLayer(layer) && isFeatureLayer(layer))
56
+ }
57
+
58
+ export function isTerrainLayer (layer) {
59
+ if (layer.type === 'TerrainLayer') return true
60
+ const cesiumOptions = layer.cesium || layer
61
+ return (cesiumOptions.type === 'Cesium') || (cesiumOptions.type === 'Ellipsoid')
62
+ }
63
+
64
+ export async function saveGeoJsonLayer (layer, geoJson, chunkSize = 5000) {
65
+ // Check for invalid features first
66
+ const check = checkFeatures(geoJson)
67
+ if (check.kinks.length > 0) {
68
+ const result = await kCoreUtils.dialog({
69
+ title: i18n.t('utils.layers.INVALID_FEATURES_DIALOG_TITLE', { total: check.kinks.length }),
70
+ message: i18n.t('utils.layers.INVALID_FEATURES_DIALOG_MESSAGE', { total: check.kinks.length }),
71
+ options: {
72
+ type: 'toggle',
73
+ model: [],
74
+ items: [
75
+ { label: i18n.t('utils.layers.DOWNLOAD_INVALID_FEATURES_LABEL'), value: 'download' }
76
+ ]
77
+ },
78
+ html: true,
79
+ ok: {
80
+ label: i18n.t('OK'),
81
+ flat: true
82
+ },
83
+ cancel: {
84
+ label: i18n.t('CANCEL'),
85
+ flat: true
86
+ }
87
+ })
88
+ if (!result.ok) return
89
+ // Export invalid features if required
90
+ if (_.get(result, 'data', []).includes('download')) {
91
+ kCoreUtils.downloadAsBlob(JSON.stringify({ type: 'FeatureCollection', features: check.kinks }),
92
+ i18n.t('utils.layers.INVALID_FEATURES_FILE'), 'application/json;charset=utf-8;')
93
+ }
94
+ }
95
+ // Change data source from in-memory to features service
96
+ _.set(layer, 'service', 'features')
97
+ if (_.has(layer, 'leaflet')) _.set(layer, 'leaflet.source', '/api/features')
98
+ if (_.has(layer, 'cesium')) _.set(layer, 'cesium.source', '/api/features')
99
+ const features = (geoJson.type === 'FeatureCollection' ? geoJson.features : [geoJson])
100
+ // If too much data use tiling
101
+ // The threshold is based on the number of points and not features.
102
+ // Indeed otherwise the complexity will be different depending on the geometry type
103
+ // (eg a bucket of 1000 polygons can actually contains a lot of points).
104
+ let nbPoints = 0
105
+ features.forEach(feature => {
106
+ nbPoints += explode(feature).features.length
107
+ })
108
+ if (nbPoints > 5000) {
109
+ _.set(layer, 'leaflet.tiled', true)
110
+ _.set(layer, 'leaflet.minZoom', 15)
111
+ }
112
+ Loading.show({ message: i18n.t('utils.layers.SAVING_LABEL', { processed: 0, total: features.length }), html: true })
113
+ let createdLayer
114
+ try {
115
+ createdLayer = await api.getService('catalog')
116
+ .create(_.omit(layer, InternalLayerProperties))
117
+ let nbFeatures = 0
118
+ // We use the generated DB ID as layer ID on features
119
+ await createFeatures(geoJson, createdLayer._id, chunkSize, (i, chunk) => {
120
+ // Update saving message according to new chunk data
121
+ nbFeatures += chunk.length
122
+ Loading.show({
123
+ message: i18n.t('utils.layers.SAVING_LABEL', { processed: nbFeatures, total: features.length }),
124
+ html: true
125
+ })
126
+ })
127
+ // Because we save all features in a single service use filtering to separate layers
128
+ createdLayer = await api.getService('catalog').patch(createdLayer._id, { baseQuery: { layer: createdLayer._id } })
129
+ if (_.get(layer, 'leaflet.tiled')) {
130
+ Notify.create({ type: 'positive', message: i18n.t('utils.layers.SAVE_DIALOG_MESSAGE'), timeout: 10000, html: true })
131
+ }
132
+ } catch (error) {
133
+ // User error message on operation should be raised by error hook, otherwise this is more a coding error
134
+ logger.error(`[KDK] ${error}`)
135
+ }
136
+ Loading.hide()
137
+ return createdLayer
138
+ }
139
+
140
+ export async function saveLayer (layer) {
141
+ await api.getService('catalog').create(_.omit(layer, InternalLayerProperties))
142
+ }
143
+
144
+ export async function removeLayer (layer) {
145
+ const result = await kCoreUtils.dialog({
146
+ title: i18n.t('utils.layers.REMOVE_DIALOG_TITLE', { layer: layer.label || layer.name }),
147
+ message: i18n.t('utils.layers.REMOVE_DIALOG_MESSAGE', { layer: layer.label || layer.name }),
148
+ html: true,
149
+ ok: {
150
+ label: i18n.t('OK'),
151
+ flat: true
152
+ },
153
+ cancel: {
154
+ label: i18n.t('CANCEL'),
155
+ flat: true
156
+ }
157
+ })
158
+ if (!result.ok) return false
159
+ Loading.show({ message: i18n.t('utils.layers.REMOVING_LABEL'), html: true })
160
+ try {
161
+ if (layer._id) {
162
+ // If persistent feature layer remove features as well
163
+ if (isFeatureLayer(layer)) {
164
+ await removeFeatures(layer._id)
165
+ }
166
+ await api.getService('catalog').remove(layer._id)
167
+ }
168
+ } catch (error) {
169
+ // User error message on operation should be raised by error hook, otherwise this is more a coding error
170
+ logger.error(`[KDK] ${error}`)
171
+ }
172
+ Loading.hide()
173
+ return true
174
+ }
@@ -1,7 +1,59 @@
1
- import { Store, api } from '../../../core.client.js'
2
- import { formatGeocodingResult, parseCoordinates, formatUserCoordinates } from './utils.js'
1
+ import _ from 'lodash'
2
+ import logger from 'loglevel'
3
+ import config from 'config'
4
+ import { Store, api, i18n, Events } from '../../../core.client.js'
5
+ import { formatUserCoordinates } from './utils.js'
3
6
 
4
- export async function searchLocation (pattern, options) {
7
+ // Format (reverse) geocoding output
8
+ function formatGeocodingResult (result) {
9
+ const properties = result.properties
10
+ if (!properties) {
11
+ logger.warn('[KDK] invalid geocoding result: missing \'properties\' property')
12
+ return
13
+ }
14
+ // check whether the result as a valid formatted address
15
+ let label = properties.formattedAddress || ''
16
+ // try to build a formatted address
17
+ if (!label) {
18
+ if (properties.streetNumber) label += (properties.streetNumber + ', ')
19
+ if (properties.streetName) label += (properties.streetName + ' ')
20
+ if (properties.city) label += (properties.city + ' ')
21
+ if (properties.zipcode) label += (' (' + properties.zipcode + ')')
22
+ }
23
+ // otherwise retireve the match prop
24
+ if (!label) {
25
+ if (!_.has(result, 'geokoder.matchProp')) {
26
+ logger.warn('[KDK] invalid geocoding result: missing \'geokoder.matchProp\' property')
27
+ return
28
+ }
29
+ label = _.get(result, result.geokoder.matchProp, '')
30
+ }
31
+ return label
32
+ }
33
+
34
+ export function parseCoordinates (str) {
35
+ const coords = _.split(_.trim(str), ',')
36
+ if (coords.length !== 2) return
37
+ const latitude = Number(coords[0])
38
+ if (_.isNaN(latitude)) return
39
+ const longitude = Number(coords[1])
40
+ if (_.isNaN(longitude)) return
41
+ return {
42
+ latitude,
43
+ longitude
44
+ }
45
+ }
46
+
47
+ export async function queryGeocoder(planetConfig, path, query = '') {
48
+ const endpoint = planetConfig.gateway + '/geocoder'
49
+ const jwt = await api.get('storage').getItem(planetConfig.gatewayJwt)
50
+ let url = `${endpoint}/${path}`
51
+ if (query) url += `?${query}`
52
+ const results = await fetch(url, { headers: { Authorization: `Bearer ${jwt}` } }).then((response) => response.json())
53
+ return results
54
+ }
55
+
56
+ export async function searchLocation (planetConfig, pattern, options) {
5
57
  const locations = []
6
58
  // Try to parse lat/long coordinates
7
59
  const coordinates = parseCoordinates(pattern)
@@ -17,29 +69,45 @@ export async function searchLocation (pattern, options) {
17
69
  }
18
70
  })
19
71
  } else {
20
- // TODO
21
- // Use the geocoders list in the options object to perform the query to the service
22
- // The new result should be an array of GeoJSON so the following formatting should be removed
23
- const geocoderService = api.getService('geocoder')
24
- if (!geocoderService) throw new Error('Cannot find geocoder service')
25
- const response = await geocoderService.create({ address: pattern })
26
- response.forEach(element => {
27
- locations.push({
28
- type: 'Feature',
29
- geometry: {
30
- type: 'Point',
31
- coordinates: [element.longitude, element.latitude]
32
- },
33
- properties: {
34
- name: formatGeocodingResult(element)
35
- }
36
- })
72
+ let filter = ''
73
+ if (!_.isEmpty(options.geocoders)) {
74
+ // only request geocoder results from specified sources
75
+ filter = '&sources=*(' + options.geocoders.join('|') + ')'
76
+ }
77
+ const results = await queryGeocoder(planetConfig, 'forward', `q=${pattern}${filter}`)
78
+ results.forEach(result => {
79
+ locations.push(
80
+ Object.assign(
81
+ _.pick(result, ['type', 'geometry']),
82
+ { properties: { name: formatGeocodingResult(result), source: result.geokoder.source } }))
37
83
  })
38
84
  }
39
85
  return locations
40
86
  }
41
87
 
42
- export async function listGeocoders () {
43
- // TODO
44
- return ['nominatin', 'ban']
88
+ export async function listGeocoders (planetConfig) {
89
+ let response
90
+ try {
91
+ response = await queryGeocoder(planetConfig, 'capabilities/forward')
92
+ if (response.i18n) i18n.registerTranslation(response.i18n)
93
+ } catch (error) {
94
+ Events.emit('error', { message: i18n.t('errors.NETWORK_ERROR') })
95
+ }
96
+ return _.get(response, 'geocoders', [])
45
97
  }
98
+
99
+ // Filter geocoder sources based on given project
100
+ export function filterGeocoders(geocoders, project) {
101
+ return geocoders.filter(geocoder => {
102
+ // Geocoder id can directly be a string or the value of UI elements with label/value
103
+ const id = geocoder.value || geocoder
104
+ if (project && id.startsWith('kano:')) {
105
+ const service = id.replace('kano:', '')
106
+ // Depending on the layer the geocoding source (i.e. collection/service) is not the same
107
+ let layer = _.find(project.layers, { service })
108
+ if (!layer) layer = _.find(project.layers, { probeService: service })
109
+ return layer
110
+ }
111
+ return true
112
+ })
113
+ }
@@ -0,0 +1,8 @@
1
+ import _ from 'lodash'
2
+
3
+ export function getCatalogProjectQuery (project) {
4
+ // As we keep track of ID/name depending on if a layer is a user-defined one or not we need to process both
5
+ const idQuery = { _id: { $in: _.map(_.filter(project.layers, '_id'), '_id') } }
6
+ const nameQuery = { name: { $in: _.map(_.filter(project.layers, 'name'), 'name') } }
7
+ return { $or: [idQuery, nameQuery] }
8
+ }
@@ -38,7 +38,6 @@ export function generatePropertiesSchema (geoJson, name) {
38
38
  component: (type === 'number'
39
39
  ? 'form/KNumberField'
40
40
  : (type === 'boolean' ? 'form/KToggleField' : 'form/KTextField')),
41
- helper: key,
42
41
  label: key
43
42
  }
44
43
  }