@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
@@ -0,0 +1,364 @@
1
+ import _ from 'lodash'
2
+ import logger from 'loglevel'
3
+ import moment from 'moment'
4
+ import { getType } from '@turf/invariant'
5
+ import explode from '@turf/explode'
6
+ import kinks from '@turf/kinks'
7
+ import clean from '@turf/clean-coords'
8
+ import rhumbBearing from '@turf/rhumb-bearing'
9
+ import rhumbDistance from '@turf/rhumb-distance'
10
+ import rotate from '@turf/transform-rotate'
11
+ import scale from '@turf/transform-scale'
12
+ import translate from '@turf/transform-translate'
13
+ import { api, Time } from '../../../core/client/index.js'
14
+
15
+ export function processFeatures (geoJson, processor) {
16
+ const features = (geoJson.type === 'FeatureCollection' ? geoJson.features : [geoJson])
17
+ if (typeof processor === 'function') {
18
+ features.forEach(feature => processor(feature))
19
+ } else if (typeof processor === 'string') {
20
+ const compiler = _.template(processor)
21
+ features.forEach(feature => compiler({ feature, properties: feature.properties }))
22
+ }
23
+ }
24
+
25
+ export function transformFeatures (geoJson, transform) {
26
+ const features = (geoJson.type === 'FeatureCollection' ? geoJson.features : [geoJson])
27
+ features.forEach(feature => {
28
+ const scaling = _.get(transform, 'scale')
29
+ const rotation = _.get(transform, 'rotate')
30
+ const translation = _.get(transform, 'translate')
31
+ if (scaling) {
32
+ scale(feature, scaling.factor,
33
+ Object.assign(_.omit(scaling, ['factor']), { mutate: true }))
34
+ }
35
+ if (rotation) {
36
+ rotate(feature, rotation.angle,
37
+ Object.assign(_.omit(rotation, ['angle']), { mutate: true }))
38
+ }
39
+ if (translation) {
40
+ // Could be expressed as direction/distance or target point
41
+ // Take care that turfjs uses a rhumb line
42
+ if (translation.point) {
43
+ translation.distance = rhumbDistance(translation.pivot || [0, 0], translation.point)
44
+ translation.direction = rhumbBearing(translation.pivot || [0, 0], translation.point)
45
+ delete translation.pivot
46
+ delete translation.point
47
+ }
48
+ translate(feature, translation.distance, translation.direction,
49
+ Object.assign(_.omit(translation, ['direction', 'distance']), { mutate: true }))
50
+ }
51
+ })
52
+ }
53
+
54
+ export async function getBaseQueryForFeatures (options) {
55
+ // Any base query to process ?
56
+ const baseQuery = {}
57
+ if (options.baseQuery) {
58
+ if (typeof options.baseQuery === 'function') {
59
+ const result = await options.baseQuery()
60
+ // A null query indicate to skip update
61
+ if (!result) return
62
+ else Object.assign(baseQuery, result)
63
+ } else {
64
+ Object.assign(baseQuery, options.baseQuery)
65
+ }
66
+ }
67
+ return baseQuery
68
+ }
69
+
70
+ export async function getFilterQueryForFeatures (options) {
71
+ // Any filter query to process ?
72
+ const filterQuery = {}
73
+ if (options.filterQuery) {
74
+ if (typeof options.filterQuery === 'function') {
75
+ const result = await options.filterQuery()
76
+ Object.assign(filterQuery, result)
77
+ } else {
78
+ Object.assign(filterQuery, options.filterQuery)
79
+ }
80
+ }
81
+ // Any filters to process ?
82
+ const filters = _.get(options, 'filters', [])
83
+ if (!_.isEmpty(filters)) {
84
+ // To be flexible enough filters can provide a query for their active and inactive state
85
+ // Similarly, filters can be combined with a different operator for each state
86
+ const filterOperators = _.get(options, 'filterOperators', { active: '$or', inactive: '$and' })
87
+ // Aggregate filter queries according to filter states
88
+ const activeFilters = filters
89
+ .filter(filter => filter.isActive)
90
+ .map(filter => filter.active)
91
+ .filter(query => !_.isEmpty(query))
92
+ if (!_.isEmpty(activeFilters)) filterQuery[filterOperators.active] = activeFilters
93
+ const inactiveFilters = filters
94
+ .filter(filter => !filter.isActive)
95
+ .map(filter => filter.inactive)
96
+ .filter(query => !_.isEmpty(query))
97
+ if (!_.isEmpty(inactiveFilters)) filterQuery[filterOperators.inactive] = inactiveFilters
98
+ }
99
+ return filterQuery
100
+ }
101
+
102
+ export async function getSortQueryForFeatures (options) {
103
+ // Any sort query to process ?
104
+ const sortQuery = {}
105
+ if (options.sortQuery) {
106
+ if (typeof options.sortQuery === 'function') {
107
+ const result = await options.sortQuery()
108
+ Object.assign(sortQuery, result)
109
+ } else {
110
+ Object.assign(sortQuery, options.sortQuery)
111
+ }
112
+ }
113
+ return { $sort: sortQuery }
114
+ }
115
+
116
+ export function getFeaturesUpdateInterval (options) {
117
+ const interval = _.get(options, 'every')
118
+ return (interval ? moment.duration(interval) : null)
119
+ }
120
+
121
+ export function getFeaturesQueryInterval (options) {
122
+ const interval = getFeaturesUpdateInterval(options)
123
+ let queryInterval = _.get(options, 'queryFrom')
124
+ // If query interval not given use 2 x refresh interval as default value
125
+ // this ensures we cover last interval if server/client update processes are not in sync
126
+ if (!queryInterval && interval) queryInterval = moment.duration(-2 * interval.asMilliseconds())
127
+ return (queryInterval ? moment.duration(queryInterval) : null)
128
+ }
129
+
130
+ export function shouldSkipFeaturesUpdate (lastUpdateTime, options, interval) {
131
+ // If not given try to compute query interval from options
132
+ if (!interval) {
133
+ interval = getFeaturesUpdateInterval(options)
134
+ }
135
+ // We assume this is not a time-varying layer
136
+ if (!interval) return true
137
+ const now = Time.getCurrentTime()
138
+ const elapsed = moment.duration(now.diff(lastUpdateTime))
139
+ // If query interval has elapsed since last update we need to update again
140
+ return (Math.abs(elapsed.asMilliseconds()) < interval.asMilliseconds())
141
+ }
142
+
143
+ export async function getProbeFeatures (options) {
144
+ // Any base/filter/sort query to process ?
145
+ const query = await getBaseQueryForFeatures(options)
146
+ const filterQuery = await getFilterQueryForFeatures(options)
147
+ const sortQuery = await getSortQueryForFeatures(options)
148
+ Object.assign(query, filterQuery, sortQuery)
149
+ // Check API to be used in case the layer is coming from a remote "planet"
150
+ const planetApi = (typeof options.getPlanetApi === 'function' ? options.getPlanetApi() : api)
151
+ const response = await planetApi.getService(options.probeService).find(Object.assign({ query }, options.baseParams))
152
+ if (options.processor) processFeatures(response, options.processor)
153
+ if (options.transform) transformFeatures(response, options.transform)
154
+ return response
155
+ }
156
+
157
+ export async function getFeaturesQuery (options, queryInterval, queryLevel) {
158
+ // If not given try to compute query interval from options
159
+ if (!queryInterval) {
160
+ queryInterval = getFeaturesQueryInterval(options)
161
+ }
162
+ // Any base query to process ?
163
+ let query = await getBaseQueryForFeatures(options)
164
+ // Request features with at least one data available during last query interval
165
+ if (queryInterval) {
166
+ // Check if we have variables to be aggregated in time or not
167
+ if (options.variables) {
168
+ query = Object.assign({
169
+ $groupBy: options.featureId,
170
+ // Take care we might have multiple variables targetting the same value name
171
+ // but that differentiate using others properties (compound feature ID)
172
+ $aggregate: _.uniq(options.variables.map(variable => variable.name))
173
+ }, query)
174
+ } else if (options.featureId) {
175
+ query = Object.assign({
176
+ $groupBy: options.featureId,
177
+ $aggregate: ['geometry']
178
+ }, query)
179
+ }
180
+ const now = Time.getCurrentTime()
181
+ if (moment.isDuration(queryInterval)) {
182
+ // Depending on the duration format we might have negative or positive values
183
+ const gte = (queryInterval.asMilliseconds() > 0
184
+ ? now.clone().subtract(queryInterval)
185
+ : now.clone().add(queryInterval))
186
+ const lte = now
187
+ Object.assign(query, {
188
+ $sort: { time: -1, runTime: -1 },
189
+ time: {
190
+ $gte: gte.toISOString(),
191
+ $lte: lte.toISOString()
192
+ }
193
+ })
194
+ // If we can aggregate then keep track of last element of each aggregation
195
+ if (options.featureId) query.$limit = 1
196
+ } else if (typeof queryInterval === 'object') {
197
+ query.time = queryInterval
198
+ } else {
199
+ Object.assign(query, {
200
+ $sort: { time: -1, runTime: -1 },
201
+ time: { $lte: now.toISOString() }
202
+ })
203
+ // If we can aggregate then keep track of last element of each aggregation
204
+ if (options.featureId) query.$limit = 1
205
+ }
206
+ }
207
+ if (!_.isNil(queryLevel)) {
208
+ query.level = queryLevel
209
+ }
210
+ // Any filter/sort query to process ?
211
+ const filterQuery = await getFilterQueryForFeatures(options)
212
+ const sortQuery = await getSortQueryForFeatures(options)
213
+ // Take care to not erase possible existing sort options
214
+ _.merge(query, filterQuery, sortQuery)
215
+
216
+ return query
217
+ }
218
+
219
+ export async function getFeaturesFromQuery (options, query) {
220
+ // Check API to be used in case the layer is coming from a remote "planet"
221
+ const planetApi = (typeof options.getPlanetApi === 'function' ? options.getPlanetApi() : api)
222
+ const response = await planetApi.getService(options.service).find(Object.assign({ query }, options.baseParams))
223
+ if (options.processor) processFeatures(response, options.processor)
224
+ if (options.transform) transformFeatures(response, options.transform)
225
+ return response
226
+ }
227
+
228
+ export function checkFeatures (geoJson, options = {
229
+ kinks: true,
230
+ redundantCoordinates: true
231
+ }) {
232
+ const features = (geoJson.type === 'FeatureCollection' ? geoJson.features : [geoJson])
233
+ // Removes redundant coordinates
234
+ if (options.redundantCoordinates) {
235
+ features.forEach(feature => clean(feature, { mutate: true }))
236
+ }
237
+ // Filter invalid features
238
+ let kinksFeatures
239
+ if (options.kinks) {
240
+ kinksFeatures = _.remove(features, feature => {
241
+ const type = getType(feature)
242
+ if ((type === 'MultiPolygon') || (type === 'Polygon')) {
243
+ const invalidFeatures = kinks(feature)
244
+ return _.get(invalidFeatures, 'features', []).length > 0
245
+ } else { // No possible self-intersection on points, self-intersections allowed on lines
246
+ return false
247
+ }
248
+ })
249
+ }
250
+ return { kinks: kinksFeatures }
251
+ }
252
+
253
+ export async function createFeatures (geoJson, layerId, chunkSize = 5000, processCallback) {
254
+ if (!layerId) return
255
+ const features = (geoJson.type === 'FeatureCollection' ? geoJson.features : [geoJson])
256
+ features.forEach(feature => {
257
+ // Remove any temporary ID as we will use the one from MongoDB
258
+ delete feature._id
259
+ feature.layer = layerId
260
+ })
261
+ // Single edition mode
262
+ if (features.length === 1) {
263
+ const feature = await api.getService('features').create(features[0])
264
+ return feature
265
+ } else {
266
+ // Create chunks to avoid reaching some limits (upload size, timeout, etc.)
267
+ // We create chunks according to the number of points and not features.
268
+ // Indeed otherwise it would create very different chunks depending on the geometry type
269
+ // (eg a bucket of 1000 polygons can actually contains a lot of points).
270
+ // const chunks = _.chunk(features, chunkSize)
271
+ const chunks = []
272
+ let chunkPoints = 0
273
+ let chunk = []
274
+ features.forEach(feature => {
275
+ const explodedFeature = explode(feature)
276
+ const nbPoints = explodedFeature.features.length
277
+ // Check if current chunk is not full
278
+ if ((chunkPoints + nbPoints) <= chunkSize) {
279
+ chunkPoints += nbPoints
280
+ chunk.push(feature)
281
+ } else {
282
+ // Otherwise push current chunk if not empty
283
+ if (chunk.length > 0) {
284
+ chunks.push(chunk)
285
+ }
286
+ // Then start new chunk
287
+ chunk = [feature]
288
+ chunkPoints = nbPoints
289
+ }
290
+ })
291
+ // Push last chunk
292
+ if (chunk.length > 0) {
293
+ chunks.push(chunk)
294
+ }
295
+ // Write the chunks
296
+ for (let i = 0; i < chunks.length; i++) {
297
+ await api.getService('features').create(chunks[i])
298
+ if (typeof processCallback === 'function') await processCallback(i, chunks[i])
299
+ }
300
+ }
301
+ }
302
+
303
+ export async function editFeaturesGeometry (geoJson) {
304
+ const features = (geoJson.type === 'FeatureCollection' ? geoJson.features : [geoJson])
305
+ const updatedFeatures = []
306
+ for (let i = 0; i < features.length; i++) {
307
+ const feature = features[i]
308
+ if (feature._id) {
309
+ const updatedFeature = await api.getService('features').patch(feature._id, _.pick(feature, ['geometry']))
310
+ updatedFeatures.push(updatedFeature)
311
+ }
312
+ }
313
+ return (geoJson.type === 'FeatureCollection' ? Object.assign(geoJson, { features: updatedFeatures }) : updatedFeatures)
314
+ }
315
+
316
+ export async function editFeaturesProperties (geoJson) {
317
+ const features = (geoJson.type === 'FeatureCollection' ? geoJson.features : [geoJson])
318
+ const updatedFeatures = []
319
+ for (let i = 0; i < features.length; i++) {
320
+ const feature = features[i]
321
+ if (feature._id) {
322
+ const updatedFeature = await api.getService('features').patch(feature._id, _.pick(feature, ['properties']))
323
+ updatedFeatures.push(updatedFeature)
324
+ }
325
+ }
326
+ return (geoJson.type === 'FeatureCollection' ? Object.assign(geoJson, { features: updatedFeatures }) : updatedFeatures)
327
+ }
328
+
329
+ export async function removeFeatures (geoJsonOrLayerId) {
330
+ // Remove all features of a given layer
331
+ if (typeof geoJsonOrLayerId === 'string') {
332
+ await api.getService('features').remove(null, { query: { layer: geoJsonOrLayerId } })
333
+ } else {
334
+ const features = (geoJsonOrLayerId.type === 'FeatureCollection' ? geoJsonOrLayerId.features : [geoJsonOrLayerId])
335
+ for (let i = 0; i < features.length; i++) {
336
+ const feature = features[i]
337
+ if (feature._id) await api.getService('features').remove(feature._id)
338
+ }
339
+ }
340
+ }
341
+
342
+ export async function fetchGeoJson (dataSource, options = {}) {
343
+ const response = await fetch(dataSource)
344
+ if (response.status !== 200) {
345
+ throw new Error(`Impossible to fetch ${dataSource}: ` + response.status)
346
+ }
347
+ const data = await response.json()
348
+ if (options.processor) processFeatures(data, options.processor)
349
+ if (options.transform) transformFeatures(data, options.transform)
350
+ return data
351
+ }
352
+
353
+ export function getFeatureStyleType (feature) {
354
+ const geometryType = _.get(feature, 'geometry.type')
355
+ if (!geometryType) {
356
+ logger.warn('[KDK] feature has undefined geometry')
357
+ return
358
+ }
359
+ if (geometryType === 'Point') return 'point'
360
+ if (['LineString', 'MultiLineString'].includes(geometryType)) return 'line'
361
+ if (['Polygon', 'MultiPolygon'].includes(geometryType)) return 'polygon'
362
+ logger.warn(`[KDK] unsupported geometry of type of ${geometryType}`)
363
+ return
364
+ }
@@ -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
- }