@kalisio/kdk 2.1.9 → 2.2.1

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 (272) hide show
  1. package/.travis.test.sh +42 -10
  2. package/README.md +2 -2
  3. package/core/api/application.js +7 -2
  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/KModal.vue +1 -1
  21. package/core/client/components/KSponsor.vue +1 -1
  22. package/core/client/components/KTextArea.vue +5 -1
  23. package/core/client/components/app/KAbout.vue +1 -2
  24. package/core/client/components/app/KWelcome.vue +3 -5
  25. package/core/client/components/chart/KTimeSeriesChart.vue +24 -37
  26. package/core/client/components/collection/KColumn.vue +20 -17
  27. package/core/client/components/editor/KModalEditor.vue +0 -2
  28. package/core/client/components/form/KChipsField.vue +12 -2
  29. package/core/client/components/form/KColorField.vue +12 -2
  30. package/core/client/components/form/KColorScaleField.vue +12 -2
  31. package/core/client/components/form/KDateTimeRangeField.vue +12 -2
  32. package/core/client/components/form/KDatetimeField.vue +12 -2
  33. package/core/client/components/form/KEmailField.vue +12 -2
  34. package/core/client/components/form/KFileField.vue +12 -2
  35. package/core/client/components/form/KForm.vue +47 -9
  36. package/core/client/components/form/KIconField.vue +12 -2
  37. package/core/client/components/form/KItemField.vue +25 -4
  38. package/core/client/components/form/KNumberField.vue +12 -2
  39. package/core/client/components/form/KOptionsField.vue +12 -2
  40. package/core/client/components/form/KPasswordField.vue +12 -2
  41. package/core/client/components/form/KPhoneField.vue +13 -3
  42. package/core/client/components/form/KPropertyItemField.vue +12 -2
  43. package/core/client/components/form/KResolutionField.vue +126 -0
  44. package/core/client/components/form/KRoleField.vue +12 -2
  45. package/core/client/components/form/KSelectField.vue +14 -4
  46. package/core/client/components/form/KTextField.vue +12 -2
  47. package/core/client/components/form/KTextareaField.vue +13 -3
  48. package/core/client/components/form/KToggleField.vue +12 -2
  49. package/core/client/components/form/KTokenField.vue +12 -2
  50. package/core/client/components/form/KUnitField.vue +12 -2
  51. package/core/client/components/form/KUrlField.vue +12 -2
  52. package/core/client/components/input/KIconChooser.vue +10 -12
  53. package/core/client/components/input/KPalette.vue +2 -1
  54. package/core/client/components/layout/KPage.vue +5 -4
  55. package/core/client/components/layout/KWindow.vue +10 -10
  56. package/core/client/components/media/KColorScale.vue +25 -19
  57. package/core/client/components/media/KImageViewer.vue +57 -33
  58. package/core/client/components/media/KShape.vue +14 -103
  59. package/core/client/components/screen/KRegisterScreen.vue +0 -1
  60. package/core/client/components/screen/KScreenFooter.vue +0 -18
  61. package/core/client/components/team/KAddMember.vue +16 -22
  62. package/core/client/components/team/KGroupsActivity.vue +14 -0
  63. package/core/client/components/team/KMembersActivity.vue +12 -0
  64. package/core/client/components/team/KTagsActivity.vue +14 -0
  65. package/core/client/components/time/KDateTime.vue +23 -7
  66. package/core/client/components/time/KTimeControl.vue +142 -0
  67. package/core/client/components/tool/KExportTool.vue +57 -0
  68. package/core/client/composables/collection.js +0 -1
  69. package/core/client/composables/pwa.js +0 -1
  70. package/core/client/composables/schema.js +1 -1
  71. package/core/client/composables/session.js +30 -6
  72. package/core/client/exporter.js +147 -0
  73. package/core/client/i18n/core_en.json +91 -23
  74. package/core/client/i18n/core_fr.json +92 -23
  75. package/core/client/index.js +3 -0
  76. package/core/client/layout.js +34 -14
  77. package/core/client/local-storage.js +8 -6
  78. package/core/client/mixins/index.js +0 -1
  79. package/core/client/mixins/mixin.base-field.js +24 -2
  80. package/core/client/mixins/mixin.object-proxy.js +0 -1
  81. package/core/client/search.js +2 -1
  82. package/core/client/services/index.js +2 -1
  83. package/core/client/services/local-settings.service.js +4 -4
  84. package/core/client/theme.js +3 -3
  85. package/core/client/time.js +4 -0
  86. package/core/client/units.js +150 -5
  87. package/core/client/utils/index.js +13 -6
  88. package/core/client/utils/utils.account.js +1 -1
  89. package/core/client/utils/utils.colors.js +43 -0
  90. package/core/client/utils/utils.platform.js +0 -1
  91. package/core/client/utils/utils.pwa.js +14 -14
  92. package/core/client/utils/utils.session.js +1 -1
  93. package/core/client/utils/utils.shapes.js +270 -0
  94. package/core/client/utils/utils.time.js +37 -0
  95. package/core/common/permissions.js +3 -0
  96. package/core/common/schemas/settings.update.json +50 -29
  97. package/extras/css/core.variables.scss +3 -1
  98. package/extras/icons/wind-speed-0.svg +8 -0
  99. package/extras/icons/wind-speed-10.svg +8 -0
  100. package/extras/icons/wind-speed-100.svg +12 -0
  101. package/extras/icons/wind-speed-105.svg +13 -0
  102. package/extras/icons/wind-speed-15.svg +9 -0
  103. package/extras/icons/wind-speed-20.svg +9 -0
  104. package/extras/icons/wind-speed-25.svg +10 -0
  105. package/extras/icons/wind-speed-30.svg +10 -0
  106. package/extras/icons/wind-speed-35.svg +11 -0
  107. package/extras/icons/wind-speed-40.svg +11 -0
  108. package/extras/icons/wind-speed-45.svg +12 -0
  109. package/extras/icons/wind-speed-5.svg +9 -0
  110. package/extras/icons/wind-speed-50.svg +9 -0
  111. package/extras/icons/wind-speed-55.svg +10 -0
  112. package/extras/icons/wind-speed-60.svg +10 -0
  113. package/extras/icons/wind-speed-65.svg +11 -0
  114. package/extras/icons/wind-speed-70.svg +11 -0
  115. package/extras/icons/wind-speed-75.svg +12 -0
  116. package/extras/icons/wind-speed-80.svg +12 -0
  117. package/extras/icons/wind-speed-85.svg +13 -0
  118. package/extras/icons/wind-speed-90.svg +13 -0
  119. package/extras/icons/wind-speed-95.svg +14 -0
  120. package/extras/tours/map/navigation-bar.js +17 -15
  121. package/extras/tours/map/timeline.js +33 -33
  122. package/map/api/config/categories.cjs +4 -1
  123. package/map/api/hooks/hooks.catalog.js +39 -0
  124. package/map/api/hooks/hooks.features.js +23 -3
  125. package/map/api/hooks/hooks.query.js +65 -21
  126. package/map/api/models/projects.model.mongodb.js +8 -0
  127. package/map/api/services/catalog/catalog.hooks.js +5 -3
  128. package/map/api/services/features/features.hooks.js +18 -6
  129. package/map/api/services/index.js +22 -6
  130. package/map/api/services/projects/projects.hooks.js +118 -0
  131. package/map/client/capture.js +16 -0
  132. package/map/client/cesium/utils/index.js +4 -0
  133. package/map/client/cesium/utils/utils.events.js +30 -0
  134. package/map/client/cesium/utils/utils.features.js +8 -0
  135. package/map/client/cesium/utils/utils.popup.js +17 -0
  136. package/map/client/cesium/utils/utils.style.js +137 -0
  137. package/map/client/components/KCapture.vue +50 -0
  138. package/map/client/components/KCaptureTextArea.vue +53 -0
  139. package/map/client/components/KCompass.vue +2 -2
  140. package/map/client/components/KFeaturesChart.vue +1 -1
  141. package/map/client/components/KFeaturesFilter.vue +2 -2
  142. package/map/client/components/KLayerStyleForm.vue +288 -454
  143. package/map/client/components/KLevelSlider.vue +1 -1
  144. package/map/client/components/KNorth.vue +31 -0
  145. package/map/client/components/KProjectMenu.vue +88 -0
  146. package/map/client/components/KTimezoneMap.vue +36 -24
  147. package/map/client/components/catalog/KAddLayer.vue +3 -4
  148. package/map/client/components/catalog/KConnectLayer.vue +20 -4
  149. package/map/client/components/catalog/KCreateLayer.vue +1 -2
  150. package/map/client/components/catalog/KCreateProject.vue +100 -0
  151. package/map/client/components/catalog/KCreateView.vue +25 -2
  152. package/map/client/components/catalog/KLayersPanel.vue +24 -27
  153. package/map/client/components/catalog/KLayersSelector.vue +1 -1
  154. package/map/client/components/catalog/KProjectEditor.vue +91 -0
  155. package/map/client/components/catalog/KProjectManager.vue +60 -0
  156. package/map/client/components/catalog/KProjectSelector.vue +38 -0
  157. package/map/client/components/catalog/KProjectsPanel.vue +153 -0
  158. package/map/client/components/catalog/KSelectLayers.vue +96 -0
  159. package/map/client/components/catalog/KSelectViews.vue +96 -0
  160. package/map/client/components/catalog/KViewsPanel.vue +66 -30
  161. package/map/client/components/form/KDirectionField.vue +24 -5
  162. package/map/client/components/form/KLayerCategoryField.vue +12 -2
  163. package/map/client/components/form/KLocationField.vue +20 -5
  164. package/map/client/components/form/KOwsLayerField.vue +12 -2
  165. package/map/client/components/form/KOwsServiceField.vue +12 -2
  166. package/map/client/components/form/KSelectLayersField.vue +159 -0
  167. package/map/client/components/form/KSelectViewsField.vue +121 -0
  168. package/map/client/components/form/KTimezoneField.vue +24 -17
  169. package/map/client/components/legend/KColorScaleLegend.vue +6 -2
  170. package/map/client/components/legend/KLayerLegend.vue +71 -0
  171. package/map/client/components/legend/KLegend.vue +54 -51
  172. package/map/client/components/legend/KLegendRenderer.vue +5 -3
  173. package/map/client/components/legend/KSymbolsLegend.vue +12 -10
  174. package/map/client/components/legend/KVariablesLegend.vue +78 -0
  175. package/map/client/components/location/KGeocodersFilter.vue +2 -4
  176. package/map/client/components/location/KLocationCardSection.vue +8 -4
  177. package/map/client/components/location/KLocationMap.vue +48 -17
  178. package/map/client/components/location/KLocationSearch.vue +13 -3
  179. package/map/client/components/tools/KSearchTool.vue +17 -12
  180. package/map/client/components/widget/KElevationProfile.vue +16 -19
  181. package/map/client/components/widget/KMapillaryViewer.vue +21 -22
  182. package/map/client/components/widget/KTimeSeries.vue +35 -29
  183. package/map/client/composables/activity.js +15 -2
  184. package/map/client/composables/catalog.js +81 -0
  185. package/map/client/composables/highlight.js +45 -30
  186. package/map/client/composables/index.js +2 -0
  187. package/map/client/composables/location.js +25 -18
  188. package/map/client/composables/probe.js +4 -1
  189. package/map/client/composables/project.js +122 -0
  190. package/map/client/composables/weather.js +3 -3
  191. package/map/client/geolocation.js +1 -1
  192. package/map/client/globe.js +2 -0
  193. package/map/client/i18n/map_en.json +127 -76
  194. package/map/client/i18n/map_fr.json +128 -72
  195. package/map/client/index.js +3 -0
  196. package/map/client/init.js +17 -0
  197. package/map/client/leaflet/GSMaPLayer.js +16 -17
  198. package/map/client/leaflet/ShapeMarker.js +51 -0
  199. package/map/client/leaflet/TiledFeatureLayer.js +39 -9
  200. package/map/client/leaflet/TiledMeshLayer.js +13 -15
  201. package/map/client/leaflet/TiledWindLayer.js +6 -10
  202. package/map/client/leaflet/utils/index.js +4 -0
  203. package/map/client/leaflet/utils/utils.events.js +41 -0
  204. package/map/client/leaflet/utils/utils.popup.js +21 -0
  205. package/map/client/leaflet/utils/utils.style.js +195 -0
  206. package/map/client/leaflet/utils/utils.tiles.js +87 -0
  207. package/map/client/map.js +2 -0
  208. package/map/client/mixins/globe/mixin.base-globe.js +39 -18
  209. package/map/client/mixins/globe/mixin.geojson-layers.js +139 -69
  210. package/map/client/mixins/globe/mixin.popup.js +2 -1
  211. package/map/client/mixins/globe/mixin.style.js +6 -4
  212. package/map/client/mixins/globe/mixin.tooltip.js +8 -3
  213. package/map/client/mixins/map/mixin.base-map.js +53 -28
  214. package/map/client/mixins/map/mixin.edit-layers.js +15 -15
  215. package/map/client/mixins/map/mixin.forecast-layers.js +3 -1
  216. package/map/client/mixins/map/mixin.geojson-layers.js +60 -20
  217. package/map/client/mixins/map/mixin.georaster-layers.js +4 -11
  218. package/map/client/mixins/map/mixin.heatmap-layers.js +1 -1
  219. package/map/client/mixins/map/mixin.popup.js +2 -1
  220. package/map/client/mixins/map/mixin.style.js +4 -67
  221. package/map/client/mixins/map/mixin.tiled-mesh-layers.js +2 -1
  222. package/map/client/mixins/map/mixin.tiled-wind-layers.js +4 -2
  223. package/map/client/mixins/map/mixin.tooltip.js +2 -1
  224. package/map/client/mixins/mixin.activity.js +71 -192
  225. package/map/client/mixins/mixin.catalog-panel.js +6 -6
  226. package/map/client/mixins/mixin.context.js +12 -9
  227. package/map/client/mixins/mixin.feature-service.js +29 -300
  228. package/map/client/mixins/mixin.weacast.js +11 -17
  229. package/map/client/pixi-utils.js +1 -1
  230. package/map/client/planets.js +66 -0
  231. package/map/client/utils/index.js +6 -0
  232. package/map/client/utils/utils.capture.js +176 -0
  233. package/map/client/utils/utils.catalog.js +166 -0
  234. package/map/client/utils/utils.features.js +364 -0
  235. package/map/client/utils/utils.js +0 -151
  236. package/map/client/utils/utils.layers.js +175 -0
  237. package/map/client/utils/utils.location.js +91 -23
  238. package/map/client/utils/utils.project.js +8 -0
  239. package/map/client/utils/utils.schema.js +0 -1
  240. package/map/client/utils/utils.style.js +309 -0
  241. package/map/client/utils.all.js +2 -2
  242. package/map/client/utils.globe.js +1 -1
  243. package/map/client/utils.map.js +1 -1
  244. package/map/common/permissions.js +2 -0
  245. package/map/common/schemas/capture.create.json +132 -0
  246. package/map/common/schemas/projects.create.json +52 -0
  247. package/map/common/schemas/projects.update.json +52 -0
  248. package/map/common/wms-utils.js +8 -3
  249. package/package.json +6 -5
  250. package/test/api/core/account.test.js +20 -0
  251. package/test/api/core/config/default.cjs +16 -3
  252. package/test/api/core/import-export.test.js +86 -0
  253. package/test/api/core/test-log-2024-01-04.log +14 -0
  254. package/test/api/map/catalog.test.js +164 -0
  255. package/test/api/map/index.test.js +25 -61
  256. package/test/api/map/test-log-2024-01-04.log +2 -0
  257. package/test/api/map/test-log-2024-01-11.log +1 -0
  258. package/test/api/map/test-log-2024-01-25.log +19 -0
  259. package/test/client/core/layout.js +24 -5
  260. package/test/client/core/utils.js +7 -0
  261. package/test/client/map/catalog.js +78 -1
  262. package/test/client/map/time.js +2 -1
  263. package/core/client/components/screen/KEndpointScreen.vue +0 -80
  264. package/core/client/mixins/mixin.account.js +0 -61
  265. package/extras/icons/kdk.png +0 -0
  266. package/map/api/services/geocoder/geocoder.service.js +0 -79
  267. package/map/client/cesium/utils.js +0 -133
  268. package/map/client/components/KCaptureToolbar.vue +0 -155
  269. package/map/client/components/KColorLegend.vue +0 -349
  270. package/map/client/components/KTimeline.vue +0 -293
  271. package/map/client/components/KUrlLegend.vue +0 -122
  272. package/map/client/leaflet/utils.js +0 -246
@@ -1,77 +1,77 @@
1
1
  module.exports = [{
2
- target: '#timeline',
2
+ target: '#timecontrols',
3
3
  title: 'tours.timeline.TIMELINE_LABEL',
4
4
  params: {
5
5
  placement: 'top'
6
6
  }
7
7
  }, {
8
- target: '#timeline-date',
8
+ target: '#datetime-controls',
9
9
  title: 'tours.timeline.DATE_LABEL',
10
10
  params: {
11
11
  placement: 'top'
12
12
  }
13
13
  }, {
14
- target: '#timeline-now',
15
- title: 'tours.timeline.NOW_LABEL',
14
+ target: '#date-button',
15
+ title: 'tours.timeline.PICK_DAY_LABEL',
16
16
  params: {
17
- placement: 'top'
17
+ placement: 'top',
18
+ clickOn: '#date-button',
19
+ clickDelay: 100
18
20
  }
19
21
  }, {
20
- target: '#timeline-forward',
21
- title: 'tours.timeline.NEXT_STEP_LABEL',
22
- link: 'tours.timeline.SETTINGS_LINK_LABEL',
22
+ target: '#time-button',
23
+ title: 'tours.timeline.PICK_TIMES_LABEL',
23
24
  params: {
24
25
  placement: 'top',
25
- tour: 'home'
26
+ clickOn: '#time-button',
27
+ clickDelay: 100
26
28
  }
27
29
  }, {
28
- target: '#timeline-backward',
29
- title: 'tours.timeline.PREVIOUS_STEP_LABEL',
30
- link: 'tours.timeline.SETTINGS_LINK_LABEL',
30
+ target: '#timecontrol-now',
31
+ title: 'tours.timeline.NOW_LABEL',
31
32
  params: {
32
- placement: 'top',
33
- tour: 'home'
33
+ placement: 'top'
34
34
  }
35
35
  }, {
36
- target: '#timeline-minutes-1',
37
- title: 'tours.timeline.PICK_MINUTES_LABEL',
36
+ target: '#timecontrol-step',
37
+ title: 'tours.timeline.STEP_LABEL',
38
38
  params: {
39
- placement: 'top'
39
+ placement: 'top',
40
40
  }
41
41
  }, {
42
- target: '#timeline-next-hour',
43
- title: 'tours.timeline.NEXT_HOUR_LABEL',
42
+ target: '#previous-step',
43
+ title: 'tours.timeline.PREVIOUS_STEP_LABEL',
44
44
  params: {
45
- placement: 'top'
45
+ placement: 'top',
46
46
  }
47
47
  }, {
48
- target: '#timeline-previous-hour',
48
+ target: '#previous-hour',
49
49
  title: 'tours.timeline.PREVIOUS_HOUR_LABEL',
50
50
  params: {
51
- placement: 'top'
51
+ placement: 'top',
52
52
  }
53
53
  }, {
54
- target: '#timeline-hours-1',
55
- title: 'tours.timeline.PICK_HOUR_LABEL',
54
+ target: '#previous-day',
55
+ title: 'tours.timeline.PREVIOUS_DAY_LABEL',
56
56
  params: {
57
- placement: 'top'
57
+ placement: 'top',
58
58
  }
59
59
  }, {
60
- target: '#timeline-next-day',
60
+ target: '#next-day',
61
61
  title: 'tours.timeline.NEXT_DAY_LABEL',
62
62
  params: {
63
- placement: 'top'
63
+ placement: 'top',
64
64
  }
65
65
  }, {
66
- target: '#timeline-previous-day',
67
- title: 'tours.timeline.PREVIOUS_DAY_LABEL',
66
+ target: '#next-hour',
67
+ title: 'tours.timeline.NEXT_HOUR_LABEL',
68
68
  params: {
69
- placement: 'top'
69
+ placement: 'top',
70
70
  }
71
71
  }, {
72
- target: '#timeline-days-1',
73
- title: 'tours.timeline.PICK_DAY_LABEL',
72
+ target: '#next-step',
73
+ title: 'tours.timeline.NEXT_STEP_LABEL',
74
74
  params: {
75
- placement: 'top'
75
+ placement: 'top',
76
76
  }
77
77
  }]
@@ -21,7 +21,10 @@ module.exports = function (categoryFiles, context) {
21
21
  categories = categories.concat(categoriesFromFile)
22
22
  })
23
23
  debug(`Found ${categories.length} category definitions to build catalog from`)
24
-
24
+ // Sort categories
25
+ categories.sort((category, other) => {
26
+ return (category.order || 50) - (other.order || 50)
27
+ })
25
28
  // All categories by default
26
29
  let filter = categories.map(category => category.name.replace('Categories.', ''))
27
30
  // Now build filter according any env filter
@@ -83,3 +83,42 @@ export async function updateLayerReferences (hook) {
83
83
  debug(`Updated ${contexts.length} contexts and categories after renaming or removing layer ${layer.name} `)
84
84
  return hook
85
85
  }
86
+
87
+ // Update projects when a layer/view is removed
88
+ export async function updateProjects (hook) {
89
+ const app = hook.app
90
+ const context = hook.service.getContextId()
91
+ const projectsService = app.getService('projects', context)
92
+ if (!projectsService) return hook
93
+ let removedItems = getItems(hook)
94
+ if (!Array.isArray(removedItems)) removedItems = [removedItems]
95
+ for (let i = 0; i < removedItems.length; i++) {
96
+ const removedItem = removedItems[i]
97
+ const isLayer = removedItem.type !== 'Context'
98
+ const query = {}
99
+ if (isLayer) {
100
+ query.$or = [{ 'layers._id': removedItem._id }, { 'layers.name': removedItem.name }]
101
+ } else {
102
+ query['views._id'] = removedItem._id
103
+ }
104
+ // Retrieve the list of all projects involving the item
105
+ const projects = await projectsService.find({
106
+ query, paginate: false
107
+ })
108
+ // Stop when non found
109
+ if (projects.length === 0) {
110
+ debug(`No project to update after removing item ${removedItem.name} `)
111
+ return hook
112
+ }
113
+ // Update each project otherwise
114
+ await Promise.all(projects.map(project => {
115
+ // Remove item in list
116
+ const items = (isLayer ? project.layers : project.views)
117
+ _.remove(items, item => item._id ? removedItem._id.toString() === item._id.toString() : removedItem.name === item.name)
118
+ return projectsService.patch(project._id.toString(), isLayer ? { layers: items } : { views: items })
119
+ }))
120
+
121
+ debug(`Updated ${projects.length} projects after removing item ${removedItem.name} `)
122
+ }
123
+ return hook
124
+ }
@@ -4,6 +4,7 @@ import bbox from '@turf/bbox'
4
4
  import makeDebug from 'debug'
5
5
  import feathers from '@feathersjs/feathers'
6
6
  import common from 'feathers-hooks-common'
7
+ import mongodbFuzzySearch from 'feathers-mongodb-fuzzy-search'
7
8
  import { hooks as coreHooks, unmarshallTime } from '../../../core/api/index.js'
8
9
 
9
10
  const { defaultEventMap } = feathers
@@ -31,12 +32,16 @@ function getLayers (features) {
31
32
  }
32
33
 
33
34
  export function simplifyResult (hook) {
35
+ // Force full result emission ?
36
+ const fullResult = _.get(hook, 'params.fullResult')
37
+ if (fullResult) return hook
34
38
  const service = hook.service
35
39
  const method = hook.method
36
40
  const result = getItems(hook)
37
41
  const simplifyResult = _.get(service, 'options.simplifyResult', ['create', 'update', 'patch', 'remove'])
42
+ const simplifyResultLimit = _.get(service, 'options.simplifyResultLimit', 1)
38
43
  // Only keep track of object IDs so that caller can at least get them if required
39
- if (simplifyResult.includes(method) && Array.isArray(result)) {
44
+ if (simplifyResult.includes(method) && Array.isArray(result) && (result.length > simplifyResultLimit)) {
40
45
  debug(`Simplifying multi result for ${method} method on service ${service.name}`)
41
46
  replaceItems(hook, result.map(item => ({ _id: item._id })))
42
47
  }
@@ -44,6 +49,9 @@ export function simplifyResult (hook) {
44
49
  }
45
50
 
46
51
  export function skipEvents (hook) {
52
+ // Force standard event emission ?
53
+ const emitEvents = _.get(hook, 'params.emitEvents')
54
+ if (emitEvents) return hook
47
55
  const service = hook.service
48
56
  const method = hook.method
49
57
  const event = defaultEventMap[method]
@@ -52,12 +60,16 @@ export function skipEvents (hook) {
52
60
  const simplifyEvents = _.get(service, 'options.simplifyEvents', [])
53
61
  // Even if we emit simplified events we skip standard feathers events to emit our own version
54
62
  let skip = skipEvents.includes(event) || simplifyEvents.includes(event)
55
- skip = skip && Array.isArray(result)
63
+ const skipEventsLimit = _.get(service, 'options.skipEventsLimit', 1)
64
+ skip = skip && Array.isArray(result) && (result.length > skipEventsLimit)
56
65
  if (skip) debug(`Skipping standard event ${event} for multi operation on service ${service.name}`)
57
66
  return iff(hook => skip, coreHooks.skipEvents)(hook)
58
67
  }
59
68
 
60
69
  export function simplifyEvents (hook) {
70
+ // Force standard event emission ?
71
+ const emitEvents = _.get(hook, 'params.emitEvents')
72
+ if (emitEvents) return hook
61
73
  const service = hook.service
62
74
  const params = hook.params
63
75
  const data = hook.data
@@ -67,7 +79,8 @@ export function simplifyEvents (hook) {
67
79
  const event = defaultEventMap[method]
68
80
  const result = getItems(hook)
69
81
  const simplifyEvents = _.get(service, 'options.simplifyEvents', [])
70
- if (simplifyEvents.includes(event) && Array.isArray(result)) {
82
+ const simplifyEventsLimit = _.get(service, 'options.simplifyEventsLimit', 1)
83
+ if (simplifyEvents.includes(event) && Array.isArray(result) && (result.length > simplifyEventsLimit)) {
71
84
  debug(`Simplifying event ${event} for multi operation on service ${service.name}`)
72
85
  // Keep track of query selector when updating/patching/removing in batch
73
86
  const payload = (event === 'created' ? {} : { data })
@@ -82,3 +95,10 @@ export function simplifyEvents (hook) {
82
95
  }
83
96
  return hook
84
97
  }
98
+
99
+ export function fuzzySearch (hook) {
100
+ const service = hook.service
101
+ const featureLabel = _.castArray(_.get(service, 'options.featureLabel', []))
102
+ const fields = featureLabel.map((prop) => 'properties.' + prop)
103
+ return featureLabel.length ? mongodbFuzzySearch({ fields: fields })(hook) : hook
104
+ }
@@ -12,6 +12,35 @@ export function marshallGeometryQuery (hook) {
12
12
  marshallGeometry(geometry)
13
13
  }
14
14
 
15
+ function getGeometryQueryForBBox (bbox) {
16
+ // Here we use the custom MongoDB CRS that enforces counter-clockwise winding order
17
+ // and allows to support queries with a single-ringed GeoJSON polygon
18
+ // whose area is greater than or equal to a single hemisphere.
19
+ // Otherwise $geoIntersects queries for the complementary geometry.
20
+ return {
21
+ $geoIntersects: {
22
+ $geometry: {
23
+ type: 'Polygon',
24
+ coordinates: [bbox],
25
+ crs: {
26
+ type: 'name',
27
+ properties: { name: 'urn:x-mongodb:crs:strictwinding:EPSG:4326' }
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
33
+
34
+ export function marshallGeoJsonQuery (hook) {
35
+ const query = hook.params.query
36
+ if (query) {
37
+ if (query.geoJson) {
38
+ delete query.geoJson
39
+ hook.params.asGeoJson = true
40
+ }
41
+ }
42
+ }
43
+
15
44
  export function marshallSpatialQuery (hook) {
16
45
  const query = hook.params.query
17
46
  if (query) {
@@ -66,17 +95,20 @@ export function marshallSpatialQuery (hook) {
66
95
  delete query.north
67
96
  delete query.west
68
97
  delete query.east
69
- const geometryQuery = {
70
- $geoIntersects: {
71
- $geometry: {
72
- type: 'Polygon',
73
- coordinates: [ // BBox as a polygon
74
- [[west, south], [east, south], [east, north], [west, north], [west, south]] // Closing point
75
- ]
76
- }
77
- }
98
+
99
+ // FIXME: MongoDB should allow to support queries with a single-ringed GeoJSON polygon
100
+ // whose area is greater than or equal to a single hemisphere.
101
+ // However, we did not succeeed in making it work as expected, for now on we split large polygon into two halfs.
102
+ if ((east-west) <= 180) {
103
+ // BBox as a polygon also requires the closing point.
104
+ const bbox = [[west, south], [east, south], [east, north], [west, north], [west, south]]
105
+ query.geometry = getGeometryQueryForBBox(bbox)
106
+ } else {
107
+ // BBox as a polygon also requires the closing point.
108
+ const leftHalfBbox = [[west, south], [0.5*(west+east), south], [0.5*(west+east), north], [west, north], [west, south]]
109
+ const rightHalfBbox = [[0.5*(west+east), south], [east, south], [east, north], [0.5*(west+east), north], [0.5*(west+east), south]]
110
+ query.$or = [{ geometry: getGeometryQueryForBBox(leftHalfBbox) }, { geometry: getGeometryQueryForBBox(rightHalfBbox) }]
78
111
  }
79
- query.geometry = geometryQuery
80
112
  }
81
113
  // Shortcut for location query
82
114
  if (!_.isNil(query.longitude) && !_.isNil(query.latitude)) {
@@ -95,11 +127,9 @@ export function marshallSpatialQuery (hook) {
95
127
  }
96
128
  query.geometry = geometryQuery
97
129
  }
98
- if (query.geoJson) {
99
- delete query.geoJson
100
- hook.params.asGeoJson = true
101
- }
102
130
  }
131
+ // Include GeoJson query by default
132
+ marshallGeoJsonQuery(hook)
103
133
  }
104
134
 
105
135
  export function asGeoJson (options = {}) {
@@ -118,6 +148,9 @@ export function asGeoJson (options = {}) {
118
148
  const isPaginated = !_.isNil(results.data)
119
149
  const pagination = (isPaginated ? _.pick(results, ['total', 'skip', 'limit']) : {})
120
150
  results = (isPaginated ? results.data : results)
151
+ // Single item case, i.e. GET ?
152
+ const isFeatureCollection = Array.isArray(results)
153
+ if (!isFeatureCollection) results = [results]
121
154
  results = results
122
155
  .filter(item => {
123
156
  // Check if item is not already in GeoJson feature format and we can convert if required
@@ -169,24 +202,35 @@ export function asGeoJson (options = {}) {
169
202
  // Move some data to properties ?
170
203
  if (options.properties) {
171
204
  results.forEach(item => {
172
- options.properties.forEach(mapping => {
173
- if (mapping.from) _.set(item, `properties.${mapping.to || mapping.from}`, _.get(item, `${mapping.from}`))
174
- if (mapping.delete) _.unset(item, `${mapping.from}`)
175
- })
205
+ // True indicates to move all fields to properties
206
+ if (options.properties === true) {
207
+ _.forOwn(item, (value, key) => {
208
+ if ((key === 'geometry') || (key === 'type') || (key === '_id')) return
209
+ _.set(item, `properties.${key}`, _.get(item, key))
210
+ _.unset(item, key)
211
+ })
212
+ } else { // Else we expect a specific mapping
213
+ options.properties.forEach(mapping => {
214
+ if (mapping.from) _.set(item, `properties.${mapping.to || mapping.from}`, _.get(item, `${mapping.from}`))
215
+ if (mapping.delete) _.unset(item, `${mapping.from}`)
216
+ })
217
+ }
176
218
  })
177
219
  }
178
- // Copy pagination information if any so that client can use it anyway
179
- if (_.get(options, 'asFeatureCollection', true)) {
220
+ // If we should make it available as a GeoJson feature collection create it
221
+ if (isFeatureCollection && _.get(options, 'asFeatureCollection', true)) {
222
+ // Copy pagination information if any so that client can use it anyway
180
223
  _.set(hook, options.dataPath || 'result', Object.assign({
181
224
  type: 'FeatureCollection',
182
225
  features: results
183
226
  }, pagination)) // If no pagination this merged object will be empty
184
227
  } else if (isPaginated) {
228
+ // Copy pagination information if any so that client can use it anyway
185
229
  _.set(hook, options.dataPath || 'result', Object.assign({
186
230
  data: results
187
231
  }, pagination))
188
232
  } else {
189
- _.set(hook, options.dataPath || 'result', results)
233
+ _.set(hook, options.dataPath || 'result', isFeatureCollection ? results : results[0])
190
234
  }
191
235
  }
192
236
  }
@@ -0,0 +1,8 @@
1
+ export default function (app, options) {
2
+ const db = options.db || app.db
3
+ options.Model = db.collection('projects')
4
+ // Collation provided in query ensure sorting to be case insensitive w.r.t. user's language
5
+ // We built indices with collation to cover the most used languages, it requires different naming...
6
+ options.Model.createIndex({ name: 1 }, { name: 'name-en', collation: { locale: 'en', strength: 1 } })
7
+ options.Model.createIndex({ name: 1 }, { name: 'name-fr', collation: { locale: 'fr', strength: 1 } })
8
+ }
@@ -1,7 +1,8 @@
1
1
  import _ from 'lodash'
2
2
  import common from 'feathers-hooks-common'
3
+ import fuzzySearch from 'feathers-mongodb-fuzzy-search'
3
4
  import { hooks as coreHooks } from '../../../../core/api/index.js'
4
- import { filterLayers, updateLayerReferences, getDefaultCategories, getDefaultSublegends } from '../../hooks/index.js'
5
+ import { filterLayers, updateLayerReferences, updateProjects, getDefaultCategories, getDefaultSublegends } from '../../hooks/index.js'
5
6
 
6
7
  const { setNow, discard } = common
7
8
 
@@ -9,7 +10,7 @@ export default {
9
10
  before: {
10
11
  all: [],
11
12
  find: [
12
- filterLayers, coreHooks.distinct
13
+ fuzzySearch({ fields: ['name'] }), coreHooks.diacriticSearch(), filterLayers, coreHooks.distinct
13
14
  ],
14
15
  get: [],
15
16
  create: [
@@ -60,7 +61,8 @@ export default {
60
61
  ],
61
62
  remove: [
62
63
  setNow('updatedAt'),
63
- updateLayerReferences
64
+ updateLayerReferences,
65
+ updateProjects
64
66
  ]
65
67
  },
66
68
 
@@ -1,19 +1,31 @@
1
+ import _ from 'lodash'
1
2
  import { hooks as coreHooks } from '../../../../core/api/index.js'
2
3
  import {
3
4
  marshallSpatialQuery, aggregateFeaturesQuery, asGeoJson,
4
- simplifyResult, simplifyEvents, skipEvents
5
+ simplifyResult, simplifyEvents, skipEvents, fuzzySearch
5
6
  } from '../../hooks/index.js'
6
7
 
8
+ // Allow to control real-time events emission if required
9
+ const emitEvents = (hook) => {
10
+ _.set(hook, 'params.emitEvents', _.get(hook, 'params.query.emitEvents'))
11
+ _.unset(hook, 'params.query.emitEvents')
12
+ }
13
+ // Allow to control full result emission if required
14
+ const fullResult = (hook) => {
15
+ _.set(hook, 'params.fullResult', _.get(hook, 'params.query.fullResult'))
16
+ _.unset(hook, 'params.query.fullResult')
17
+ }
18
+
7
19
  export default {
8
20
  before: {
9
21
  all: [coreHooks.marshallTimeQuery, coreHooks.convertObjectIDs(['layer'])],
10
22
  find: [coreHooks.marshallComparisonQuery, coreHooks.marshallSortQuery, marshallSpatialQuery,
11
- coreHooks.distinct, aggregateFeaturesQuery, coreHooks.aggregationQuery],
23
+ coreHooks.distinct, aggregateFeaturesQuery, coreHooks.aggregationQuery, fuzzySearch, coreHooks.diacriticSearch],
12
24
  get: [],
13
- create: [coreHooks.processTimes(['time'])],
14
- update: [coreHooks.processTimes(['time'])],
15
- patch: [coreHooks.processTimes(['time'])],
16
- remove: [coreHooks.marshallComparisonQuery, marshallSpatialQuery]
25
+ create: [coreHooks.processTimes(['time']), fullResult, emitEvents],
26
+ update: [coreHooks.processTimes(['time']), fullResult, emitEvents],
27
+ patch: [coreHooks.processTimes(['time']), fullResult, emitEvents],
28
+ remove: [coreHooks.marshallComparisonQuery, marshallSpatialQuery, fullResult, emitEvents]
17
29
  },
18
30
 
19
31
  after: {
@@ -18,7 +18,7 @@ export function createFeaturesService (options = {}) {
18
18
 
19
19
  debug('Creating features service with options', options)
20
20
  return app.createService(options.collection, Object.assign({
21
- fileName: 'features',
21
+ modelName: 'features',
22
22
  servicesPath,
23
23
  modelsPath,
24
24
  paginate: { default: 5000, max: 5000 },
@@ -50,6 +50,22 @@ export function removeCatalogService (options = {}) {
50
50
  return app.removeService(app.getService('catalog', options.context))
51
51
  }
52
52
 
53
+ export async function createProjectsService (options = {}) {
54
+ const app = this
55
+
56
+ debug('Creating projects service with options', options)
57
+ await app.createService('projects', Object.assign({
58
+ servicesPath,
59
+ modelsPath,
60
+ paginate: { default: 20, max: 5000 }
61
+ }, options))
62
+ }
63
+
64
+ export function removeProjectsService (options = {}) {
65
+ const app = this
66
+ return app.removeService(app.getService('projects', options.context))
67
+ }
68
+
53
69
  export function createAlertsService (options = {}) {
54
70
  const app = this
55
71
 
@@ -126,13 +142,13 @@ export async function createDefaultCatalogLayers (options) {
126
142
  featuresService = await createFeaturesServiceForLayer.call(app, Object.assign({
127
143
  collection: defaultLayer.service,
128
144
  db: app.db.db(defaultLayer.dbName)
129
- }, _.pick(defaultLayer, ['ttl', 'featureId', 'featureIdType', 'variables', 'simplifyResult', 'skipEvents', 'simplifyEvents'])))
145
+ }, _.pick(defaultLayer, ['ttl', 'featureId', 'featureIdType', 'featureLabel', 'variables', 'simplifyResult', 'skipEvents', 'simplifyEvents'])))
130
146
  }
131
147
  if (defaultLayer.probeService) {
132
148
  await createFeaturesServiceForLayer.call(app, Object.assign({
133
149
  collection: defaultLayer.probeService,
134
150
  db: app.db.db(defaultLayer.dbName)
135
- }, _.pick(defaultLayer, ['simplifyResult', 'skipEvents', 'simplifyEvents'])))
151
+ }, _.pick(defaultLayer, ['featureLabel', 'simplifyResult', 'skipEvents', 'simplifyEvents'])))
136
152
  }
137
153
  // And if we need to initialize some data as well
138
154
  if (!createdLayer && featuresService && (defaultLayer.url || defaultLayer.fileName)) {
@@ -183,9 +199,9 @@ export default async function () {
183
199
  if (catalogConfig) {
184
200
  await createCatalogService.call(app)
185
201
  }
186
- const geocoderConfig = app.get('geocoder')
187
- if (geocoderConfig) {
188
- await app.createService('geocoder', { servicesPath })
202
+ const projectsConfig = app.get('projects')
203
+ if (projectsConfig) {
204
+ await createProjectsService.call(app)
189
205
  }
190
206
  // Add app-specific hooks to required services
191
207
  app.on('service', async service => {
@@ -0,0 +1,118 @@
1
+ import _ from 'lodash'
2
+ import commonHooks from 'feathers-hooks-common'
3
+ // import { hooks as schemaHooks, resolve } from '@feathersjs/schema'
4
+ import { hooks as coreHooks } from '../../../../core/api/index.js'
5
+
6
+ const { setNow, discard, getItems, replaceItems, when } = commonHooks
7
+
8
+ /* Populate is too much specialized and does not allow to merge input/output
9
+ but we need the service information on the fronted
10
+ export const populateLayers = populate({
11
+ schema: hook => {
12
+ const service = hook.app.getService('catalog')
13
+ return {
14
+ include: [{
15
+ service: service.getPath(true),
16
+ nameAs: 'layers',
17
+ select: (hook, project) => ({ _id: { $in: _.map(project.layers, '_id') } })
18
+ }]
19
+ }
20
+ }
21
+ })
22
+ */
23
+
24
+ /* Can't get resolvers to work
25
+ const layersResolver = resolve({
26
+ layers: async (project, context) => {
27
+ // Populate the layers associated via ids
28
+ const layers = await context.app.getService('catalog').find({ query: { _id: [project.layers] } })
29
+ return layers
30
+ }
31
+ })
32
+ const contextsResolver = resolve({
33
+ contexts: async (project, context) => {
34
+ // Populate the contexts associated via ids
35
+ const contexts = await context.app.getService('catalog').find({ query: { _id: [project.contexts], type: 'Context' } })
36
+ return contexts
37
+ }
38
+ })
39
+ */
40
+
41
+ function marshallPopulateQuery (hook) {
42
+ const query = hook.params.query
43
+ if (query) {
44
+ if (query.populate) {
45
+ delete query.populate
46
+ hook.params.populate = true
47
+ }
48
+ }
49
+ }
50
+
51
+ const populateProjects = async (hook) => {
52
+ let items = getItems(hook)
53
+ const isArray = Array.isArray(items)
54
+ items = (isArray ? items : [items])
55
+ for (let i = 0; i < items.length; i++) {
56
+ const project = items[i]
57
+ const layers = project.layers || []
58
+ for (let j = 0; j < layers.length; j++) {
59
+ let layer = layers[j]
60
+ // Get only name when listing
61
+ const query = { $select: ['name', 'service', 'probeService'] }
62
+ const service = hook.app.getService('catalog', _.has(layer, 'context') ? layer.context : hook.service.context)
63
+ // As we keep track of ID/name depending on if a layer is a user-defined one or not we need to process both
64
+ Object.assign(query, (layer._id ? { _id: layer._id } : { name: layer.name }))
65
+ const response = await service.find({ query })
66
+ layer = _.get(response, 'data[0]')
67
+ Object.assign(layers[j], layer)
68
+ }
69
+ const views = project.views || []
70
+ for (let j = 0; j < views.length; j++) {
71
+ let view = views[j]
72
+ // Get only name when listing
73
+ const query = { $select: ['name'], type: 'Context' }
74
+ Object.assign(query, { _id: view._id })
75
+ const service = hook.app.getService('catalog', _.has(view, 'context') ? view.context : hook.service.context)
76
+ const response = await service.find({ query })
77
+ view = _.get(response, 'data[0]')
78
+ Object.assign(views[j], view)
79
+ }
80
+ }
81
+ replaceItems(hook, isArray ? items : items[0])
82
+ }
83
+
84
+ export default {
85
+ before: {
86
+ all: [],
87
+ find: [marshallPopulateQuery],
88
+ get: [marshallPopulateQuery],
89
+ create: [coreHooks.convertObjectIDs(['layers', 'views']), setNow('createdAt', 'updatedAt')],
90
+ update: [],
91
+ patch: [coreHooks.convertObjectIDs(['layers', 'views']), discard('createdAt', 'updatedAt'), setNow('updatedAt')],
92
+ remove: []
93
+ },
94
+
95
+ after: {
96
+ all: [],
97
+ find: [when(hook => hook.params.populate, populateProjects)],
98
+ get: [when(hook => hook.params.populate, populateProjects)],
99
+ create: [],
100
+ update: [],
101
+ patch: [],
102
+ remove: []
103
+ },
104
+ /* Can't get resolvers to work
105
+ around: {
106
+ all: [schemaHooks.resolveResult(layersResolver), schemaHooks.resolveResult(contextsResolver)]
107
+ },
108
+ */
109
+ error: {
110
+ all: [],
111
+ find: [],
112
+ get: [],
113
+ create: [],
114
+ update: [],
115
+ patch: [],
116
+ remove: [setNow('updatedAt')]
117
+ }
118
+ }
@@ -0,0 +1,16 @@
1
+ import { Notify } from 'quasar'
2
+ import { capture } from './utils/utils.capture.js'
3
+ import { i18n } from '../../core/client/index.js'
4
+
5
+ // Export singleton
6
+ export const Capture = {
7
+ processing: false,
8
+ async process (values) {
9
+ if (this.processing) Notify.create({ type: 'negative', message: i18n.t('KCapture.ERROR_MESSAGE') })
10
+ else {
11
+ this.processing = true
12
+ await capture(values)
13
+ this.processing = false
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,4 @@
1
+ export * from './utils.events.js'
2
+ export * from './utils.popup.js'
3
+ export * from './utils.style.js'
4
+ export * from './utils.features.js'