@kalisio/kdk 2.5.2 → 2.5.3

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 (275) hide show
  1. package/core/client/components/input/KShapePicker.vue +4 -4
  2. package/coverage/core/api/application.js.html +344 -344
  3. package/coverage/core/api/authentication.js.html +79 -79
  4. package/coverage/core/api/db.js.html +167 -167
  5. package/coverage/core/api/hooks/hooks.authentication.js.html +12 -12
  6. package/coverage/core/api/hooks/hooks.authorisations.js.html +166 -163
  7. package/coverage/core/api/hooks/hooks.groups.js.html +1 -1
  8. package/coverage/core/api/hooks/hooks.logger.js.html +18 -18
  9. package/coverage/core/api/hooks/hooks.model.js.html +280 -268
  10. package/coverage/core/api/hooks/hooks.organisations.js.html +1 -1
  11. package/coverage/core/api/hooks/hooks.push.js.html +12 -12
  12. package/coverage/core/api/hooks/hooks.query.js.html +92 -92
  13. package/coverage/core/api/hooks/hooks.schemas.js.html +13 -13
  14. package/coverage/core/api/hooks/hooks.service.js.html +28 -28
  15. package/coverage/core/api/hooks/hooks.storage.js.html +7 -7
  16. package/coverage/core/api/hooks/hooks.users.js.html +48 -48
  17. package/coverage/core/api/hooks/index.html +49 -49
  18. package/coverage/core/api/hooks/index.js.html +11 -11
  19. package/coverage/core/api/index.html +42 -42
  20. package/coverage/core/api/index.js.html +22 -22
  21. package/coverage/core/api/marshall.js.html +127 -127
  22. package/coverage/core/api/models/groups.model.mongodb.js.html +1 -1
  23. package/coverage/core/api/models/index.html +1 -1
  24. package/coverage/core/api/models/messages.model.mongodb.js.html +1 -1
  25. package/coverage/core/api/models/organisations.model.mongodb.js.html +1 -1
  26. package/coverage/core/api/models/tags.model.mongodb.js.html +1 -1
  27. package/coverage/core/api/models/users.model.mongodb.js.html +11 -11
  28. package/coverage/core/api/services/account/account.hooks.js.html +42 -42
  29. package/coverage/core/api/services/account/account.service.js.html +34 -34
  30. package/coverage/core/api/services/account/index.html +1 -1
  31. package/coverage/core/api/services/authorisations/authorisations.hooks.js.html +34 -34
  32. package/coverage/core/api/services/authorisations/authorisations.service.js.html +108 -120
  33. package/coverage/core/api/services/authorisations/index.html +19 -19
  34. package/coverage/core/api/services/databases/databases.hooks.js.html +1 -1
  35. package/coverage/core/api/services/databases/databases.service.js.html +1 -1
  36. package/coverage/core/api/services/databases/index.html +1 -1
  37. package/coverage/core/api/services/groups/groups.hooks.js.html +1 -1
  38. package/coverage/core/api/services/groups/index.html +1 -1
  39. package/coverage/core/api/services/import-export/import-export.hooks.js.html +1 -1
  40. package/coverage/core/api/services/import-export/import-export.service.js.html +1 -1
  41. package/coverage/core/api/services/import-export/index.html +1 -1
  42. package/coverage/core/api/services/index.html +15 -15
  43. package/coverage/core/api/services/index.js.html +97 -85
  44. package/coverage/core/api/services/mailer/index.html +1 -1
  45. package/coverage/core/api/services/mailer/mailer.hooks.js.html +1 -1
  46. package/coverage/core/api/services/mailer/mailer.service.js.html +1 -1
  47. package/coverage/core/api/services/messages/index.html +5 -5
  48. package/coverage/core/api/services/messages/messages.hooks.js.html +11 -8
  49. package/coverage/core/api/services/organisations/index.html +1 -1
  50. package/coverage/core/api/services/organisations/organisations.hooks.js.html +1 -1
  51. package/coverage/core/api/services/organisations/organisations.service.js.html +1 -1
  52. package/coverage/core/api/services/push/index.html +1 -1
  53. package/coverage/core/api/services/push/push.hooks.js.html +1 -1
  54. package/coverage/core/api/services/push/push.service.js.html +1 -1
  55. package/coverage/core/api/services/storage/index.html +1 -1
  56. package/coverage/core/api/services/storage/storage.hooks.js.html +36 -36
  57. package/coverage/core/api/services/storage/storage.service.js.html +30 -30
  58. package/coverage/core/api/services/tags/index.html +1 -1
  59. package/coverage/core/api/services/tags/tags.hooks.js.html +1 -1
  60. package/coverage/core/api/services/users/index.html +7 -7
  61. package/coverage/core/api/services/users/users.hooks.js.html +83 -80
  62. package/coverage/core/api/services/users/users.service.js.html +4 -4
  63. package/coverage/core/api/utils.js.html +1 -1
  64. package/coverage/core/common/errors.js.html +2 -2
  65. package/coverage/core/common/index.html +30 -30
  66. package/coverage/core/common/index.js.html +11 -11
  67. package/coverage/core/common/permissions.js.html +187 -187
  68. package/coverage/core/common/schema.js.html +26 -26
  69. package/coverage/core/common/utils.js.html +61 -61
  70. package/coverage/core/common/utils.offline.js.html +6 -6
  71. package/coverage/index.html +179 -164
  72. package/coverage/lcov-report/core/api/application.js.html +344 -344
  73. package/coverage/lcov-report/core/api/authentication.js.html +79 -79
  74. package/coverage/lcov-report/core/api/db.js.html +167 -167
  75. package/coverage/lcov-report/core/api/hooks/hooks.authentication.js.html +12 -12
  76. package/coverage/lcov-report/core/api/hooks/hooks.authorisations.js.html +166 -163
  77. package/coverage/lcov-report/core/api/hooks/hooks.groups.js.html +1 -1
  78. package/coverage/lcov-report/core/api/hooks/hooks.logger.js.html +18 -18
  79. package/coverage/lcov-report/core/api/hooks/hooks.model.js.html +280 -268
  80. package/coverage/lcov-report/core/api/hooks/hooks.organisations.js.html +1 -1
  81. package/coverage/lcov-report/core/api/hooks/hooks.push.js.html +12 -12
  82. package/coverage/lcov-report/core/api/hooks/hooks.query.js.html +92 -92
  83. package/coverage/lcov-report/core/api/hooks/hooks.schemas.js.html +13 -13
  84. package/coverage/lcov-report/core/api/hooks/hooks.service.js.html +28 -28
  85. package/coverage/lcov-report/core/api/hooks/hooks.storage.js.html +7 -7
  86. package/coverage/lcov-report/core/api/hooks/hooks.users.js.html +48 -48
  87. package/coverage/lcov-report/core/api/hooks/index.html +49 -49
  88. package/coverage/lcov-report/core/api/hooks/index.js.html +11 -11
  89. package/coverage/lcov-report/core/api/index.html +42 -42
  90. package/coverage/lcov-report/core/api/index.js.html +22 -22
  91. package/coverage/lcov-report/core/api/marshall.js.html +127 -127
  92. package/coverage/lcov-report/core/api/models/groups.model.mongodb.js.html +1 -1
  93. package/coverage/lcov-report/core/api/models/index.html +1 -1
  94. package/coverage/lcov-report/core/api/models/messages.model.mongodb.js.html +1 -1
  95. package/coverage/lcov-report/core/api/models/organisations.model.mongodb.js.html +1 -1
  96. package/coverage/lcov-report/core/api/models/tags.model.mongodb.js.html +1 -1
  97. package/coverage/lcov-report/core/api/models/users.model.mongodb.js.html +11 -11
  98. package/coverage/lcov-report/core/api/services/account/account.hooks.js.html +42 -42
  99. package/coverage/lcov-report/core/api/services/account/account.service.js.html +34 -34
  100. package/coverage/lcov-report/core/api/services/account/index.html +1 -1
  101. package/coverage/lcov-report/core/api/services/authorisations/authorisations.hooks.js.html +34 -34
  102. package/coverage/lcov-report/core/api/services/authorisations/authorisations.service.js.html +108 -120
  103. package/coverage/lcov-report/core/api/services/authorisations/index.html +19 -19
  104. package/coverage/lcov-report/core/api/services/databases/databases.hooks.js.html +1 -1
  105. package/coverage/lcov-report/core/api/services/databases/databases.service.js.html +1 -1
  106. package/coverage/lcov-report/core/api/services/databases/index.html +1 -1
  107. package/coverage/lcov-report/core/api/services/groups/groups.hooks.js.html +1 -1
  108. package/coverage/lcov-report/core/api/services/groups/index.html +1 -1
  109. package/coverage/lcov-report/core/api/services/import-export/import-export.hooks.js.html +1 -1
  110. package/coverage/lcov-report/core/api/services/import-export/import-export.service.js.html +1 -1
  111. package/coverage/lcov-report/core/api/services/import-export/index.html +1 -1
  112. package/coverage/lcov-report/core/api/services/index.html +15 -15
  113. package/coverage/lcov-report/core/api/services/index.js.html +97 -85
  114. package/coverage/lcov-report/core/api/services/mailer/index.html +1 -1
  115. package/coverage/lcov-report/core/api/services/mailer/mailer.hooks.js.html +1 -1
  116. package/coverage/lcov-report/core/api/services/mailer/mailer.service.js.html +1 -1
  117. package/coverage/lcov-report/core/api/services/messages/index.html +5 -5
  118. package/coverage/lcov-report/core/api/services/messages/messages.hooks.js.html +11 -8
  119. package/coverage/lcov-report/core/api/services/organisations/index.html +1 -1
  120. package/coverage/lcov-report/core/api/services/organisations/organisations.hooks.js.html +1 -1
  121. package/coverage/lcov-report/core/api/services/organisations/organisations.service.js.html +1 -1
  122. package/coverage/lcov-report/core/api/services/push/index.html +1 -1
  123. package/coverage/lcov-report/core/api/services/push/push.hooks.js.html +1 -1
  124. package/coverage/lcov-report/core/api/services/push/push.service.js.html +1 -1
  125. package/coverage/lcov-report/core/api/services/storage/index.html +1 -1
  126. package/coverage/lcov-report/core/api/services/storage/storage.hooks.js.html +36 -36
  127. package/coverage/lcov-report/core/api/services/storage/storage.service.js.html +30 -30
  128. package/coverage/lcov-report/core/api/services/tags/index.html +1 -1
  129. package/coverage/lcov-report/core/api/services/tags/tags.hooks.js.html +1 -1
  130. package/coverage/lcov-report/core/api/services/users/index.html +7 -7
  131. package/coverage/lcov-report/core/api/services/users/users.hooks.js.html +83 -80
  132. package/coverage/lcov-report/core/api/services/users/users.service.js.html +4 -4
  133. package/coverage/lcov-report/core/api/utils.js.html +1 -1
  134. package/coverage/lcov-report/core/common/errors.js.html +2 -2
  135. package/coverage/lcov-report/core/common/index.html +30 -30
  136. package/coverage/lcov-report/core/common/index.js.html +11 -11
  137. package/coverage/lcov-report/core/common/permissions.js.html +187 -187
  138. package/coverage/lcov-report/core/common/schema.js.html +26 -26
  139. package/coverage/lcov-report/core/common/utils.js.html +61 -61
  140. package/coverage/lcov-report/core/common/utils.offline.js.html +6 -6
  141. package/coverage/lcov-report/index.html +179 -164
  142. package/coverage/lcov-report/map/api/hooks/hooks.catalog.js.html +348 -258
  143. package/coverage/lcov-report/map/api/hooks/hooks.features.js.html +218 -218
  144. package/coverage/lcov-report/map/api/hooks/hooks.query.js.html +795 -810
  145. package/coverage/lcov-report/map/api/hooks/index.html +54 -54
  146. package/coverage/lcov-report/map/api/hooks/index.js.html +16 -16
  147. package/coverage/lcov-report/map/api/index.html +32 -32
  148. package/coverage/lcov-report/map/api/index.js.html +46 -46
  149. package/coverage/lcov-report/map/api/marshall.js.html +72 -72
  150. package/coverage/lcov-report/map/api/models/alerts.model.mongodb.js.html +24 -24
  151. package/coverage/lcov-report/map/api/models/catalog.model.mongodb.js.html +69 -27
  152. package/coverage/lcov-report/map/api/models/features.model.mongodb.js.html +80 -80
  153. package/coverage/lcov-report/map/api/models/index.html +69 -54
  154. package/coverage/lcov-report/map/api/models/projects.model.mongodb.js.html +26 -26
  155. package/coverage/lcov-report/map/api/models/styles.model.mongodb.js.html +112 -0
  156. package/coverage/lcov-report/map/api/services/alerts/alerts.hooks.js.html +136 -136
  157. package/coverage/lcov-report/map/api/services/alerts/alerts.service.js.html +343 -343
  158. package/coverage/lcov-report/map/api/services/alerts/index.html +32 -32
  159. package/coverage/lcov-report/map/api/services/catalog/catalog.hooks.js.html +177 -162
  160. package/coverage/lcov-report/map/api/services/catalog/index.html +21 -21
  161. package/coverage/lcov-report/map/api/services/daptiles/daptiles.service.js.html +1 -1
  162. package/coverage/lcov-report/map/api/services/daptiles/index.html +1 -1
  163. package/coverage/lcov-report/map/api/services/features/features.hooks.js.html +160 -160
  164. package/coverage/lcov-report/map/api/services/features/features.service.js.html +117 -117
  165. package/coverage/lcov-report/map/api/services/features/index.html +31 -31
  166. package/coverage/lcov-report/map/api/services/index.html +21 -21
  167. package/coverage/lcov-report/map/api/services/index.js.html +404 -296
  168. package/coverage/lcov-report/map/api/services/projects/index.html +21 -21
  169. package/coverage/lcov-report/map/api/services/projects/projects.hooks.js.html +237 -237
  170. package/coverage/lcov-report/map/api/services/styles/index.html +116 -0
  171. package/coverage/lcov-report/map/api/services/styles/styles.hooks.js.html +196 -0
  172. package/coverage/lcov-report/map/common/dynamic-grid-source.js.html +68 -68
  173. package/coverage/lcov-report/map/common/errors.js.html +16 -16
  174. package/coverage/lcov-report/map/common/geotiff-grid-source.js.html +267 -267
  175. package/coverage/lcov-report/map/common/grid.js.html +554 -554
  176. package/coverage/lcov-report/map/common/index.html +158 -158
  177. package/coverage/lcov-report/map/common/index.js.html +68 -68
  178. package/coverage/lcov-report/map/common/meteo-model-grid-source.js.html +73 -73
  179. package/coverage/lcov-report/map/common/moment-utils.js.html +18 -18
  180. package/coverage/lcov-report/map/common/opendap-grid-source.js.html +484 -484
  181. package/coverage/lcov-report/map/common/opendap-utils.js.html +353 -353
  182. package/coverage/lcov-report/map/common/permissions.js.html +42 -36
  183. package/coverage/lcov-report/map/common/time-based-grid-source.js.html +59 -59
  184. package/coverage/lcov-report/map/common/tms-utils.js.html +6 -6
  185. package/coverage/lcov-report/map/common/wcs-grid-source.js.html +190 -190
  186. package/coverage/lcov-report/map/common/wcs-utils.js.html +339 -339
  187. package/coverage/lcov-report/map/common/weacast-grid-source.js.html +345 -345
  188. package/coverage/lcov-report/map/common/wfs-utils.js.html +11 -11
  189. package/coverage/lcov-report/map/common/wms-utils.js.html +8 -8
  190. package/coverage/lcov-report/map/common/wmts-utils.js.html +7 -7
  191. package/coverage/lcov.info +7022 -5894
  192. package/coverage/map/api/hooks/hooks.catalog.js.html +348 -258
  193. package/coverage/map/api/hooks/hooks.features.js.html +218 -218
  194. package/coverage/map/api/hooks/hooks.query.js.html +795 -810
  195. package/coverage/map/api/hooks/index.html +54 -54
  196. package/coverage/map/api/hooks/index.js.html +16 -16
  197. package/coverage/map/api/index.html +32 -32
  198. package/coverage/map/api/index.js.html +46 -46
  199. package/coverage/map/api/marshall.js.html +72 -72
  200. package/coverage/map/api/models/alerts.model.mongodb.js.html +24 -24
  201. package/coverage/map/api/models/catalog.model.mongodb.js.html +69 -27
  202. package/coverage/map/api/models/features.model.mongodb.js.html +80 -80
  203. package/coverage/map/api/models/index.html +69 -54
  204. package/coverage/map/api/models/projects.model.mongodb.js.html +26 -26
  205. package/coverage/map/api/models/styles.model.mongodb.js.html +112 -0
  206. package/coverage/map/api/services/alerts/alerts.hooks.js.html +136 -136
  207. package/coverage/map/api/services/alerts/alerts.service.js.html +343 -343
  208. package/coverage/map/api/services/alerts/index.html +32 -32
  209. package/coverage/map/api/services/catalog/catalog.hooks.js.html +177 -162
  210. package/coverage/map/api/services/catalog/index.html +21 -21
  211. package/coverage/map/api/services/daptiles/daptiles.service.js.html +1 -1
  212. package/coverage/map/api/services/daptiles/index.html +1 -1
  213. package/coverage/map/api/services/features/features.hooks.js.html +160 -160
  214. package/coverage/map/api/services/features/features.service.js.html +117 -117
  215. package/coverage/map/api/services/features/index.html +31 -31
  216. package/coverage/map/api/services/index.html +21 -21
  217. package/coverage/map/api/services/index.js.html +404 -296
  218. package/coverage/map/api/services/projects/index.html +21 -21
  219. package/coverage/map/api/services/projects/projects.hooks.js.html +237 -237
  220. package/coverage/map/api/services/styles/index.html +116 -0
  221. package/coverage/map/api/services/styles/styles.hooks.js.html +196 -0
  222. package/coverage/map/common/dynamic-grid-source.js.html +68 -68
  223. package/coverage/map/common/errors.js.html +16 -16
  224. package/coverage/map/common/geotiff-grid-source.js.html +267 -267
  225. package/coverage/map/common/grid.js.html +554 -554
  226. package/coverage/map/common/index.html +158 -158
  227. package/coverage/map/common/index.js.html +68 -68
  228. package/coverage/map/common/meteo-model-grid-source.js.html +73 -73
  229. package/coverage/map/common/moment-utils.js.html +18 -18
  230. package/coverage/map/common/opendap-grid-source.js.html +484 -484
  231. package/coverage/map/common/opendap-utils.js.html +353 -353
  232. package/coverage/map/common/permissions.js.html +42 -36
  233. package/coverage/map/common/time-based-grid-source.js.html +59 -59
  234. package/coverage/map/common/tms-utils.js.html +6 -6
  235. package/coverage/map/common/wcs-grid-source.js.html +190 -190
  236. package/coverage/map/common/wcs-utils.js.html +339 -339
  237. package/coverage/map/common/weacast-grid-source.js.html +345 -345
  238. package/coverage/map/common/wfs-utils.js.html +11 -11
  239. package/coverage/map/common/wms-utils.js.html +8 -8
  240. package/coverage/map/common/wmts-utils.js.html +7 -7
  241. package/coverage/tmp/coverage-151198-1753351220086-0.json +1 -0
  242. package/coverage/tmp/coverage-151210-1753351220070-0.json +1 -0
  243. package/coverage/tmp/coverage-151221-1753351129816-0.json +1 -0
  244. package/coverage/tmp/coverage-151233-1753351129803-0.json +1 -0
  245. package/coverage/tmp/coverage-151240-1753351129770-0.json +1 -0
  246. package/coverage/tmp/coverage-151307-1753351220058-0.json +1 -0
  247. package/coverage/tmp/coverage-151319-1753351220044-0.json +1 -0
  248. package/coverage/tmp/coverage-151326-1753351220010-0.json +1 -0
  249. package/map/api/services/styles/styles.hooks.js +1 -1
  250. package/map/client/components/KLayerEditor.vue +4 -0
  251. package/map/client/components/catalog/KLayerCategories.vue +2 -0
  252. package/map/client/components/stickies/KLevelSlider.vue +10 -8
  253. package/map/client/components/styles/KStyleEditor.vue +1 -1
  254. package/map/client/components/styles/KStyleManager.vue +7 -1
  255. package/map/client/composables/highlight.js +5 -1
  256. package/map/client/i18n/map_en.json +4 -1
  257. package/map/client/i18n/map_fr.json +4 -1
  258. package/map/client/mixins/globe/mixin.geojson-layers.js +11 -5
  259. package/map/client/mixins/mixin.activity.js +9 -0
  260. package/map/client/utils/utils.layers.js +0 -2
  261. package/package.json +1 -1
  262. package/test/api/core/push.test.js +1 -1
  263. package/test/api/core/test-log-2025-02-05.log +23 -0
  264. package/test/api/core/test-log-2025-05-21.log +15 -0
  265. package/test/api/core/test-log-2025-06-25.log +9 -0
  266. package/test/api/core/test-log-2025-07-24.log +44 -0
  267. package/test/api/map/test-log-2025-05-27.log +13 -0
  268. package/test/api/map/test-log-2025-06-23.log +7 -0
  269. package/test/api/map/{test-log-2025-05-26.log → test-log-2025-07-24.log} +3 -4
  270. package/coverage/tmp/coverage-122123-1739872365211-0.json +0 -1
  271. package/coverage/tmp/coverage-122135-1739872365196-0.json +0 -1
  272. package/coverage/tmp/coverage-122146-1739872365184-0.json +0 -1
  273. package/coverage/tmp/coverage-122158-1739872365169-0.json +0 -1
  274. package/coverage/tmp/coverage-122165-1739872365141-0.json +0 -1
  275. package/test/api/core/test-log-2025-05-26.log +0 -22
@@ -23,30 +23,30 @@
23
23
  <div class='clearfix'>
24
24
 
25
25
  <div class='fl pad1y space-right2'>
26
- <span class="strong">0% </span>
26
+ <span class="strong">94.29% </span>
27
27
  <span class="quiet">Statements</span>
28
- <span class='fraction'>0/408</span>
28
+ <span class='fraction'>380/403</span>
29
29
  </div>
30
30
 
31
31
 
32
32
  <div class='fl pad1y space-right2'>
33
- <span class="strong">0% </span>
33
+ <span class="strong">79.67% </span>
34
34
  <span class="quiet">Branches</span>
35
- <span class='fraction'>0/1</span>
35
+ <span class='fraction'>98/123</span>
36
36
  </div>
37
37
 
38
38
 
39
39
  <div class='fl pad1y space-right2'>
40
- <span class="strong">0% </span>
40
+ <span class="strong">100% </span>
41
41
  <span class="quiet">Functions</span>
42
- <span class='fraction'>0/1</span>
42
+ <span class='fraction'>6/6</span>
43
43
  </div>
44
44
 
45
45
 
46
46
  <div class='fl pad1y space-right2'>
47
- <span class="strong">0% </span>
47
+ <span class="strong">94.29% </span>
48
48
  <span class="quiet">Lines</span>
49
- <span class='fraction'>0/408</span>
49
+ <span class='fraction'>380/403</span>
50
50
  </div>
51
51
 
52
52
 
@@ -61,7 +61,7 @@
61
61
  </div>
62
62
  </template>
63
63
  </div>
64
- <div class='status-line low'></div>
64
+ <div class='status-line high'></div>
65
65
  <pre><table class="coverage">
66
66
  <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
67
67
  <a name='L2'></a><a href='#L2'>2</a>
@@ -466,827 +466,812 @@
466
466
  <a name='L401'></a><a href='#L401'>401</a>
467
467
  <a name='L402'></a><a href='#L402'>402</a>
468
468
  <a name='L403'></a><a href='#L403'>403</a>
469
- <a name='L404'></a><a href='#L404'>404</a>
470
- <a name='L405'></a><a href='#L405'>405</a>
471
- <a name='L406'></a><a href='#L406'>406</a>
472
- <a name='L407'></a><a href='#L407'>407</a>
473
- <a name='L408'></a><a href='#L408'>408</a>
474
- <a name='L409'></a><a href='#L409'>409</a></td><td class="line-coverage quiet"><span class="cline-any cline-no">&nbsp;</span>
475
- <span class="cline-any cline-no">&nbsp;</span>
476
- <span class="cline-any cline-no">&nbsp;</span>
477
- <span class="cline-any cline-no">&nbsp;</span>
478
- <span class="cline-any cline-no">&nbsp;</span>
479
- <span class="cline-any cline-no">&nbsp;</span>
480
- <span class="cline-any cline-no">&nbsp;</span>
481
- <span class="cline-any cline-no">&nbsp;</span>
482
- <span class="cline-any cline-no">&nbsp;</span>
483
- <span class="cline-any cline-no">&nbsp;</span>
484
- <span class="cline-any cline-no">&nbsp;</span>
485
- <span class="cline-any cline-no">&nbsp;</span>
486
- <span class="cline-any cline-no">&nbsp;</span>
487
- <span class="cline-any cline-no">&nbsp;</span>
488
- <span class="cline-any cline-no">&nbsp;</span>
489
- <span class="cline-any cline-no">&nbsp;</span>
490
- <span class="cline-any cline-no">&nbsp;</span>
491
- <span class="cline-any cline-no">&nbsp;</span>
492
- <span class="cline-any cline-no">&nbsp;</span>
493
- <span class="cline-any cline-no">&nbsp;</span>
494
- <span class="cline-any cline-no">&nbsp;</span>
495
- <span class="cline-any cline-no">&nbsp;</span>
496
- <span class="cline-any cline-no">&nbsp;</span>
497
- <span class="cline-any cline-no">&nbsp;</span>
498
- <span class="cline-any cline-no">&nbsp;</span>
499
- <span class="cline-any cline-no">&nbsp;</span>
500
- <span class="cline-any cline-no">&nbsp;</span>
501
- <span class="cline-any cline-no">&nbsp;</span>
502
- <span class="cline-any cline-no">&nbsp;</span>
503
- <span class="cline-any cline-no">&nbsp;</span>
504
- <span class="cline-any cline-no">&nbsp;</span>
505
- <span class="cline-any cline-no">&nbsp;</span>
506
- <span class="cline-any cline-no">&nbsp;</span>
507
- <span class="cline-any cline-no">&nbsp;</span>
508
- <span class="cline-any cline-no">&nbsp;</span>
509
- <span class="cline-any cline-no">&nbsp;</span>
510
- <span class="cline-any cline-no">&nbsp;</span>
511
- <span class="cline-any cline-no">&nbsp;</span>
512
- <span class="cline-any cline-no">&nbsp;</span>
513
- <span class="cline-any cline-no">&nbsp;</span>
514
- <span class="cline-any cline-no">&nbsp;</span>
515
- <span class="cline-any cline-no">&nbsp;</span>
516
- <span class="cline-any cline-no">&nbsp;</span>
517
- <span class="cline-any cline-no">&nbsp;</span>
518
- <span class="cline-any cline-no">&nbsp;</span>
519
- <span class="cline-any cline-no">&nbsp;</span>
520
- <span class="cline-any cline-no">&nbsp;</span>
521
- <span class="cline-any cline-no">&nbsp;</span>
522
- <span class="cline-any cline-no">&nbsp;</span>
523
- <span class="cline-any cline-no">&nbsp;</span>
524
- <span class="cline-any cline-no">&nbsp;</span>
525
- <span class="cline-any cline-no">&nbsp;</span>
526
- <span class="cline-any cline-no">&nbsp;</span>
527
- <span class="cline-any cline-no">&nbsp;</span>
528
- <span class="cline-any cline-no">&nbsp;</span>
529
- <span class="cline-any cline-no">&nbsp;</span>
530
- <span class="cline-any cline-no">&nbsp;</span>
531
- <span class="cline-any cline-no">&nbsp;</span>
532
- <span class="cline-any cline-no">&nbsp;</span>
533
- <span class="cline-any cline-no">&nbsp;</span>
534
- <span class="cline-any cline-no">&nbsp;</span>
535
- <span class="cline-any cline-no">&nbsp;</span>
536
- <span class="cline-any cline-no">&nbsp;</span>
537
- <span class="cline-any cline-no">&nbsp;</span>
538
- <span class="cline-any cline-no">&nbsp;</span>
539
- <span class="cline-any cline-no">&nbsp;</span>
540
- <span class="cline-any cline-no">&nbsp;</span>
541
- <span class="cline-any cline-no">&nbsp;</span>
542
- <span class="cline-any cline-no">&nbsp;</span>
543
- <span class="cline-any cline-no">&nbsp;</span>
544
- <span class="cline-any cline-no">&nbsp;</span>
545
- <span class="cline-any cline-no">&nbsp;</span>
546
- <span class="cline-any cline-no">&nbsp;</span>
547
- <span class="cline-any cline-no">&nbsp;</span>
548
- <span class="cline-any cline-no">&nbsp;</span>
549
- <span class="cline-any cline-no">&nbsp;</span>
550
- <span class="cline-any cline-no">&nbsp;</span>
551
- <span class="cline-any cline-no">&nbsp;</span>
552
- <span class="cline-any cline-no">&nbsp;</span>
553
- <span class="cline-any cline-no">&nbsp;</span>
554
- <span class="cline-any cline-no">&nbsp;</span>
555
- <span class="cline-any cline-no">&nbsp;</span>
556
- <span class="cline-any cline-no">&nbsp;</span>
557
- <span class="cline-any cline-no">&nbsp;</span>
558
- <span class="cline-any cline-no">&nbsp;</span>
559
- <span class="cline-any cline-no">&nbsp;</span>
560
- <span class="cline-any cline-no">&nbsp;</span>
561
- <span class="cline-any cline-no">&nbsp;</span>
562
- <span class="cline-any cline-no">&nbsp;</span>
563
- <span class="cline-any cline-no">&nbsp;</span>
564
- <span class="cline-any cline-no">&nbsp;</span>
565
- <span class="cline-any cline-no">&nbsp;</span>
566
- <span class="cline-any cline-no">&nbsp;</span>
567
- <span class="cline-any cline-no">&nbsp;</span>
568
- <span class="cline-any cline-no">&nbsp;</span>
569
- <span class="cline-any cline-no">&nbsp;</span>
570
- <span class="cline-any cline-no">&nbsp;</span>
571
- <span class="cline-any cline-no">&nbsp;</span>
572
- <span class="cline-any cline-no">&nbsp;</span>
573
- <span class="cline-any cline-no">&nbsp;</span>
574
- <span class="cline-any cline-no">&nbsp;</span>
575
- <span class="cline-any cline-no">&nbsp;</span>
576
- <span class="cline-any cline-no">&nbsp;</span>
577
- <span class="cline-any cline-no">&nbsp;</span>
578
- <span class="cline-any cline-no">&nbsp;</span>
579
- <span class="cline-any cline-no">&nbsp;</span>
580
- <span class="cline-any cline-no">&nbsp;</span>
581
- <span class="cline-any cline-no">&nbsp;</span>
582
- <span class="cline-any cline-no">&nbsp;</span>
583
- <span class="cline-any cline-no">&nbsp;</span>
584
- <span class="cline-any cline-no">&nbsp;</span>
585
- <span class="cline-any cline-no">&nbsp;</span>
586
- <span class="cline-any cline-no">&nbsp;</span>
587
- <span class="cline-any cline-no">&nbsp;</span>
588
- <span class="cline-any cline-no">&nbsp;</span>
589
- <span class="cline-any cline-no">&nbsp;</span>
590
- <span class="cline-any cline-no">&nbsp;</span>
591
- <span class="cline-any cline-no">&nbsp;</span>
592
- <span class="cline-any cline-no">&nbsp;</span>
593
- <span class="cline-any cline-no">&nbsp;</span>
594
- <span class="cline-any cline-no">&nbsp;</span>
595
- <span class="cline-any cline-no">&nbsp;</span>
596
- <span class="cline-any cline-no">&nbsp;</span>
597
- <span class="cline-any cline-no">&nbsp;</span>
598
- <span class="cline-any cline-no">&nbsp;</span>
599
- <span class="cline-any cline-no">&nbsp;</span>
600
- <span class="cline-any cline-no">&nbsp;</span>
601
- <span class="cline-any cline-no">&nbsp;</span>
602
- <span class="cline-any cline-no">&nbsp;</span>
603
- <span class="cline-any cline-no">&nbsp;</span>
604
- <span class="cline-any cline-no">&nbsp;</span>
605
- <span class="cline-any cline-no">&nbsp;</span>
606
- <span class="cline-any cline-no">&nbsp;</span>
607
- <span class="cline-any cline-no">&nbsp;</span>
608
- <span class="cline-any cline-no">&nbsp;</span>
609
- <span class="cline-any cline-no">&nbsp;</span>
610
- <span class="cline-any cline-no">&nbsp;</span>
611
- <span class="cline-any cline-no">&nbsp;</span>
612
- <span class="cline-any cline-no">&nbsp;</span>
613
- <span class="cline-any cline-no">&nbsp;</span>
614
- <span class="cline-any cline-no">&nbsp;</span>
615
- <span class="cline-any cline-no">&nbsp;</span>
616
- <span class="cline-any cline-no">&nbsp;</span>
617
- <span class="cline-any cline-no">&nbsp;</span>
618
- <span class="cline-any cline-no">&nbsp;</span>
619
- <span class="cline-any cline-no">&nbsp;</span>
620
- <span class="cline-any cline-no">&nbsp;</span>
621
- <span class="cline-any cline-no">&nbsp;</span>
622
- <span class="cline-any cline-no">&nbsp;</span>
623
- <span class="cline-any cline-no">&nbsp;</span>
624
- <span class="cline-any cline-no">&nbsp;</span>
625
- <span class="cline-any cline-no">&nbsp;</span>
626
- <span class="cline-any cline-no">&nbsp;</span>
627
- <span class="cline-any cline-no">&nbsp;</span>
628
- <span class="cline-any cline-no">&nbsp;</span>
629
- <span class="cline-any cline-no">&nbsp;</span>
630
- <span class="cline-any cline-no">&nbsp;</span>
631
- <span class="cline-any cline-no">&nbsp;</span>
632
- <span class="cline-any cline-no">&nbsp;</span>
633
- <span class="cline-any cline-no">&nbsp;</span>
634
- <span class="cline-any cline-no">&nbsp;</span>
635
- <span class="cline-any cline-no">&nbsp;</span>
636
- <span class="cline-any cline-no">&nbsp;</span>
637
- <span class="cline-any cline-no">&nbsp;</span>
638
- <span class="cline-any cline-no">&nbsp;</span>
639
- <span class="cline-any cline-no">&nbsp;</span>
640
- <span class="cline-any cline-no">&nbsp;</span>
641
- <span class="cline-any cline-no">&nbsp;</span>
642
- <span class="cline-any cline-no">&nbsp;</span>
643
- <span class="cline-any cline-no">&nbsp;</span>
644
- <span class="cline-any cline-no">&nbsp;</span>
645
- <span class="cline-any cline-no">&nbsp;</span>
646
- <span class="cline-any cline-no">&nbsp;</span>
647
- <span class="cline-any cline-no">&nbsp;</span>
648
- <span class="cline-any cline-no">&nbsp;</span>
649
- <span class="cline-any cline-no">&nbsp;</span>
650
- <span class="cline-any cline-no">&nbsp;</span>
651
- <span class="cline-any cline-no">&nbsp;</span>
652
- <span class="cline-any cline-no">&nbsp;</span>
653
- <span class="cline-any cline-no">&nbsp;</span>
654
- <span class="cline-any cline-no">&nbsp;</span>
655
- <span class="cline-any cline-no">&nbsp;</span>
656
- <span class="cline-any cline-no">&nbsp;</span>
657
- <span class="cline-any cline-no">&nbsp;</span>
658
- <span class="cline-any cline-no">&nbsp;</span>
659
- <span class="cline-any cline-no">&nbsp;</span>
660
- <span class="cline-any cline-no">&nbsp;</span>
661
- <span class="cline-any cline-no">&nbsp;</span>
662
- <span class="cline-any cline-no">&nbsp;</span>
663
- <span class="cline-any cline-no">&nbsp;</span>
664
- <span class="cline-any cline-no">&nbsp;</span>
665
- <span class="cline-any cline-no">&nbsp;</span>
666
- <span class="cline-any cline-no">&nbsp;</span>
667
- <span class="cline-any cline-no">&nbsp;</span>
668
- <span class="cline-any cline-no">&nbsp;</span>
669
- <span class="cline-any cline-no">&nbsp;</span>
670
- <span class="cline-any cline-no">&nbsp;</span>
671
- <span class="cline-any cline-no">&nbsp;</span>
672
- <span class="cline-any cline-no">&nbsp;</span>
673
- <span class="cline-any cline-no">&nbsp;</span>
674
- <span class="cline-any cline-no">&nbsp;</span>
675
- <span class="cline-any cline-no">&nbsp;</span>
676
- <span class="cline-any cline-no">&nbsp;</span>
677
- <span class="cline-any cline-no">&nbsp;</span>
678
- <span class="cline-any cline-no">&nbsp;</span>
679
- <span class="cline-any cline-no">&nbsp;</span>
680
- <span class="cline-any cline-no">&nbsp;</span>
681
- <span class="cline-any cline-no">&nbsp;</span>
682
- <span class="cline-any cline-no">&nbsp;</span>
683
- <span class="cline-any cline-no">&nbsp;</span>
684
- <span class="cline-any cline-no">&nbsp;</span>
685
- <span class="cline-any cline-no">&nbsp;</span>
686
- <span class="cline-any cline-no">&nbsp;</span>
687
- <span class="cline-any cline-no">&nbsp;</span>
688
- <span class="cline-any cline-no">&nbsp;</span>
689
- <span class="cline-any cline-no">&nbsp;</span>
690
- <span class="cline-any cline-no">&nbsp;</span>
691
- <span class="cline-any cline-no">&nbsp;</span>
692
- <span class="cline-any cline-no">&nbsp;</span>
693
- <span class="cline-any cline-no">&nbsp;</span>
694
- <span class="cline-any cline-no">&nbsp;</span>
695
- <span class="cline-any cline-no">&nbsp;</span>
696
- <span class="cline-any cline-no">&nbsp;</span>
697
- <span class="cline-any cline-no">&nbsp;</span>
698
- <span class="cline-any cline-no">&nbsp;</span>
699
- <span class="cline-any cline-no">&nbsp;</span>
700
- <span class="cline-any cline-no">&nbsp;</span>
701
- <span class="cline-any cline-no">&nbsp;</span>
702
- <span class="cline-any cline-no">&nbsp;</span>
703
- <span class="cline-any cline-no">&nbsp;</span>
704
- <span class="cline-any cline-no">&nbsp;</span>
705
- <span class="cline-any cline-no">&nbsp;</span>
706
- <span class="cline-any cline-no">&nbsp;</span>
707
- <span class="cline-any cline-no">&nbsp;</span>
708
- <span class="cline-any cline-no">&nbsp;</span>
709
- <span class="cline-any cline-no">&nbsp;</span>
710
- <span class="cline-any cline-no">&nbsp;</span>
711
- <span class="cline-any cline-no">&nbsp;</span>
712
- <span class="cline-any cline-no">&nbsp;</span>
713
- <span class="cline-any cline-no">&nbsp;</span>
714
- <span class="cline-any cline-no">&nbsp;</span>
715
- <span class="cline-any cline-no">&nbsp;</span>
716
- <span class="cline-any cline-no">&nbsp;</span>
717
- <span class="cline-any cline-no">&nbsp;</span>
718
- <span class="cline-any cline-no">&nbsp;</span>
719
- <span class="cline-any cline-no">&nbsp;</span>
720
- <span class="cline-any cline-no">&nbsp;</span>
721
- <span class="cline-any cline-no">&nbsp;</span>
722
- <span class="cline-any cline-no">&nbsp;</span>
723
- <span class="cline-any cline-no">&nbsp;</span>
724
- <span class="cline-any cline-no">&nbsp;</span>
725
- <span class="cline-any cline-no">&nbsp;</span>
726
- <span class="cline-any cline-no">&nbsp;</span>
727
- <span class="cline-any cline-no">&nbsp;</span>
728
- <span class="cline-any cline-no">&nbsp;</span>
729
- <span class="cline-any cline-no">&nbsp;</span>
730
- <span class="cline-any cline-no">&nbsp;</span>
731
- <span class="cline-any cline-no">&nbsp;</span>
732
- <span class="cline-any cline-no">&nbsp;</span>
733
- <span class="cline-any cline-no">&nbsp;</span>
734
- <span class="cline-any cline-no">&nbsp;</span>
735
- <span class="cline-any cline-no">&nbsp;</span>
736
- <span class="cline-any cline-no">&nbsp;</span>
737
- <span class="cline-any cline-no">&nbsp;</span>
738
- <span class="cline-any cline-no">&nbsp;</span>
739
- <span class="cline-any cline-no">&nbsp;</span>
740
- <span class="cline-any cline-no">&nbsp;</span>
741
- <span class="cline-any cline-no">&nbsp;</span>
742
- <span class="cline-any cline-no">&nbsp;</span>
743
- <span class="cline-any cline-no">&nbsp;</span>
744
- <span class="cline-any cline-no">&nbsp;</span>
745
- <span class="cline-any cline-no">&nbsp;</span>
746
- <span class="cline-any cline-no">&nbsp;</span>
747
- <span class="cline-any cline-no">&nbsp;</span>
748
- <span class="cline-any cline-no">&nbsp;</span>
749
- <span class="cline-any cline-no">&nbsp;</span>
750
- <span class="cline-any cline-no">&nbsp;</span>
751
- <span class="cline-any cline-no">&nbsp;</span>
752
- <span class="cline-any cline-no">&nbsp;</span>
753
- <span class="cline-any cline-no">&nbsp;</span>
754
- <span class="cline-any cline-no">&nbsp;</span>
755
- <span class="cline-any cline-no">&nbsp;</span>
756
- <span class="cline-any cline-no">&nbsp;</span>
757
- <span class="cline-any cline-no">&nbsp;</span>
758
- <span class="cline-any cline-no">&nbsp;</span>
759
- <span class="cline-any cline-no">&nbsp;</span>
760
- <span class="cline-any cline-no">&nbsp;</span>
761
- <span class="cline-any cline-no">&nbsp;</span>
762
- <span class="cline-any cline-no">&nbsp;</span>
763
- <span class="cline-any cline-no">&nbsp;</span>
764
- <span class="cline-any cline-no">&nbsp;</span>
765
- <span class="cline-any cline-no">&nbsp;</span>
766
- <span class="cline-any cline-no">&nbsp;</span>
767
- <span class="cline-any cline-no">&nbsp;</span>
768
- <span class="cline-any cline-no">&nbsp;</span>
769
- <span class="cline-any cline-no">&nbsp;</span>
770
- <span class="cline-any cline-no">&nbsp;</span>
771
- <span class="cline-any cline-no">&nbsp;</span>
772
- <span class="cline-any cline-no">&nbsp;</span>
773
- <span class="cline-any cline-no">&nbsp;</span>
774
- <span class="cline-any cline-no">&nbsp;</span>
775
- <span class="cline-any cline-no">&nbsp;</span>
776
- <span class="cline-any cline-no">&nbsp;</span>
777
- <span class="cline-any cline-no">&nbsp;</span>
778
- <span class="cline-any cline-no">&nbsp;</span>
779
- <span class="cline-any cline-no">&nbsp;</span>
780
- <span class="cline-any cline-no">&nbsp;</span>
781
- <span class="cline-any cline-no">&nbsp;</span>
782
- <span class="cline-any cline-no">&nbsp;</span>
783
- <span class="cline-any cline-no">&nbsp;</span>
784
- <span class="cline-any cline-no">&nbsp;</span>
785
- <span class="cline-any cline-no">&nbsp;</span>
786
- <span class="cline-any cline-no">&nbsp;</span>
787
- <span class="cline-any cline-no">&nbsp;</span>
788
- <span class="cline-any cline-no">&nbsp;</span>
789
- <span class="cline-any cline-no">&nbsp;</span>
790
- <span class="cline-any cline-no">&nbsp;</span>
791
- <span class="cline-any cline-no">&nbsp;</span>
792
- <span class="cline-any cline-no">&nbsp;</span>
793
- <span class="cline-any cline-no">&nbsp;</span>
794
- <span class="cline-any cline-no">&nbsp;</span>
795
- <span class="cline-any cline-no">&nbsp;</span>
796
- <span class="cline-any cline-no">&nbsp;</span>
797
- <span class="cline-any cline-no">&nbsp;</span>
798
- <span class="cline-any cline-no">&nbsp;</span>
799
- <span class="cline-any cline-no">&nbsp;</span>
800
- <span class="cline-any cline-no">&nbsp;</span>
801
- <span class="cline-any cline-no">&nbsp;</span>
802
- <span class="cline-any cline-no">&nbsp;</span>
803
- <span class="cline-any cline-no">&nbsp;</span>
804
- <span class="cline-any cline-no">&nbsp;</span>
805
- <span class="cline-any cline-no">&nbsp;</span>
806
- <span class="cline-any cline-no">&nbsp;</span>
807
- <span class="cline-any cline-no">&nbsp;</span>
808
- <span class="cline-any cline-no">&nbsp;</span>
809
- <span class="cline-any cline-no">&nbsp;</span>
810
- <span class="cline-any cline-no">&nbsp;</span>
811
- <span class="cline-any cline-no">&nbsp;</span>
812
- <span class="cline-any cline-no">&nbsp;</span>
813
- <span class="cline-any cline-no">&nbsp;</span>
814
- <span class="cline-any cline-no">&nbsp;</span>
815
- <span class="cline-any cline-no">&nbsp;</span>
816
- <span class="cline-any cline-no">&nbsp;</span>
817
- <span class="cline-any cline-no">&nbsp;</span>
818
- <span class="cline-any cline-no">&nbsp;</span>
819
- <span class="cline-any cline-no">&nbsp;</span>
820
- <span class="cline-any cline-no">&nbsp;</span>
821
- <span class="cline-any cline-no">&nbsp;</span>
822
- <span class="cline-any cline-no">&nbsp;</span>
823
- <span class="cline-any cline-no">&nbsp;</span>
824
- <span class="cline-any cline-no">&nbsp;</span>
825
- <span class="cline-any cline-no">&nbsp;</span>
826
- <span class="cline-any cline-no">&nbsp;</span>
827
- <span class="cline-any cline-no">&nbsp;</span>
828
- <span class="cline-any cline-no">&nbsp;</span>
829
- <span class="cline-any cline-no">&nbsp;</span>
830
- <span class="cline-any cline-no">&nbsp;</span>
831
- <span class="cline-any cline-no">&nbsp;</span>
832
- <span class="cline-any cline-no">&nbsp;</span>
833
- <span class="cline-any cline-no">&nbsp;</span>
834
- <span class="cline-any cline-no">&nbsp;</span>
835
- <span class="cline-any cline-no">&nbsp;</span>
836
- <span class="cline-any cline-no">&nbsp;</span>
837
- <span class="cline-any cline-no">&nbsp;</span>
838
- <span class="cline-any cline-no">&nbsp;</span>
839
- <span class="cline-any cline-no">&nbsp;</span>
840
- <span class="cline-any cline-no">&nbsp;</span>
841
- <span class="cline-any cline-no">&nbsp;</span>
842
- <span class="cline-any cline-no">&nbsp;</span>
843
- <span class="cline-any cline-no">&nbsp;</span>
844
- <span class="cline-any cline-no">&nbsp;</span>
845
- <span class="cline-any cline-no">&nbsp;</span>
846
- <span class="cline-any cline-no">&nbsp;</span>
847
- <span class="cline-any cline-no">&nbsp;</span>
848
- <span class="cline-any cline-no">&nbsp;</span>
849
- <span class="cline-any cline-no">&nbsp;</span>
850
- <span class="cline-any cline-no">&nbsp;</span>
851
- <span class="cline-any cline-no">&nbsp;</span>
852
- <span class="cline-any cline-no">&nbsp;</span>
853
- <span class="cline-any cline-no">&nbsp;</span>
854
- <span class="cline-any cline-no">&nbsp;</span>
855
- <span class="cline-any cline-no">&nbsp;</span>
856
- <span class="cline-any cline-no">&nbsp;</span>
857
- <span class="cline-any cline-no">&nbsp;</span>
858
- <span class="cline-any cline-no">&nbsp;</span>
859
- <span class="cline-any cline-no">&nbsp;</span>
860
- <span class="cline-any cline-no">&nbsp;</span>
861
- <span class="cline-any cline-no">&nbsp;</span>
862
- <span class="cline-any cline-no">&nbsp;</span>
863
- <span class="cline-any cline-no">&nbsp;</span>
864
- <span class="cline-any cline-no">&nbsp;</span>
865
- <span class="cline-any cline-no">&nbsp;</span>
866
- <span class="cline-any cline-no">&nbsp;</span>
867
- <span class="cline-any cline-no">&nbsp;</span>
868
- <span class="cline-any cline-no">&nbsp;</span>
869
- <span class="cline-any cline-no">&nbsp;</span>
870
- <span class="cline-any cline-no">&nbsp;</span>
871
- <span class="cline-any cline-no">&nbsp;</span>
872
- <span class="cline-any cline-no">&nbsp;</span>
873
- <span class="cline-any cline-no">&nbsp;</span>
874
- <span class="cline-any cline-no">&nbsp;</span>
875
- <span class="cline-any cline-no">&nbsp;</span>
876
- <span class="cline-any cline-no">&nbsp;</span>
877
- <span class="cline-any cline-no">&nbsp;</span>
878
- <span class="cline-any cline-no">&nbsp;</span>
879
- <span class="cline-any cline-no">&nbsp;</span>
880
- <span class="cline-any cline-no">&nbsp;</span>
881
- <span class="cline-any cline-no">&nbsp;</span>
882
- <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js"><span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" ><span class="branch-0 cbranch-no" title="branch not covered" >import _ from 'lodash'</span></span></span>
883
- <span class="cstat-no" title="statement not covered" >import { marshallGeometry } from '../marshall.js'</span>
884
- <span class="cstat-no" title="statement not covered" >import makeDebug from 'debug'</span>
885
- <span class="cstat-no" title="statement not covered" ></span>
886
- <span class="cstat-no" title="statement not covered" >const debug = makeDebug('kdk:map:query:hooks')</span>
887
- <span class="cstat-no" title="statement not covered" ></span>
888
- <span class="cstat-no" title="statement not covered" >export function marshallGeometryQuery (hook) {</span>
889
- <span class="cstat-no" title="statement not covered" > const query = hook.params.query</span>
890
- <span class="cstat-no" title="statement not covered" > if (_.isNil(query)) return</span>
891
- <span class="cstat-no" title="statement not covered" > const geometry = query.geometry</span>
892
- <span class="cstat-no" title="statement not covered" > if (_.isNil(geometry)) return</span>
893
- <span class="cstat-no" title="statement not covered" > marshallGeometry(geometry)</span>
894
- <span class="cstat-no" title="statement not covered" >}</span>
895
- <span class="cstat-no" title="statement not covered" ></span>
896
- <span class="cstat-no" title="statement not covered" >function getGeometryQueryForBBox (bbox) {</span>
897
- <span class="cstat-no" title="statement not covered" > // Here we use the custom MongoDB CRS that enforces counter-clockwise winding order</span>
898
- <span class="cstat-no" title="statement not covered" > // and allows to support queries with a single-ringed GeoJSON polygon</span>
899
- <span class="cstat-no" title="statement not covered" > // whose area is greater than or equal to a single hemisphere.</span>
900
- <span class="cstat-no" title="statement not covered" > // Otherwise $geoIntersects queries for the complementary geometry.</span>
901
- <span class="cstat-no" title="statement not covered" > return {</span>
902
- <span class="cstat-no" title="statement not covered" > $geoIntersects: {</span>
903
- <span class="cstat-no" title="statement not covered" > $geometry: {</span>
904
- <span class="cstat-no" title="statement not covered" > type: 'Polygon',</span>
905
- <span class="cstat-no" title="statement not covered" > coordinates: [bbox],</span>
906
- <span class="cstat-no" title="statement not covered" > crs: {</span>
907
- <span class="cstat-no" title="statement not covered" > type: 'name',</span>
908
- <span class="cstat-no" title="statement not covered" > properties: { name: 'urn:x-mongodb:crs:strictwinding:EPSG:4326' }</span>
909
- <span class="cstat-no" title="statement not covered" > }</span>
910
- <span class="cstat-no" title="statement not covered" > }</span>
911
- <span class="cstat-no" title="statement not covered" > }</span>
912
- <span class="cstat-no" title="statement not covered" > }</span>
913
- <span class="cstat-no" title="statement not covered" >}</span>
914
- <span class="cstat-no" title="statement not covered" ></span>
915
- <span class="cstat-no" title="statement not covered" >export function marshallGeoJsonQuery (hook) {</span>
916
- <span class="cstat-no" title="statement not covered" > const query = hook.params.query</span>
917
- <span class="cstat-no" title="statement not covered" > if (query) {</span>
918
- <span class="cstat-no" title="statement not covered" > if (query.geoJson) {</span>
919
- <span class="cstat-no" title="statement not covered" > delete query.geoJson</span>
920
- <span class="cstat-no" title="statement not covered" > hook.params.asGeoJson = true</span>
921
- <span class="cstat-no" title="statement not covered" > }</span>
922
- <span class="cstat-no" title="statement not covered" > }</span>
923
- <span class="cstat-no" title="statement not covered" >}</span>
924
- <span class="cstat-no" title="statement not covered" ></span>
925
- <span class="cstat-no" title="statement not covered" >export function marshallSpatialQuery (hook) {</span>
926
- <span class="cstat-no" title="statement not covered" > const query = hook.params.query</span>
927
- <span class="cstat-no" title="statement not covered" > if (query) {</span>
928
- <span class="cstat-no" title="statement not covered" > if (!_.isNil(query.geometry)) marshallGeometry(query.geometry)</span>
929
- <span class="cstat-no" title="statement not covered" > // Shortcut for proximity query</span>
930
- <span class="cstat-no" title="statement not covered" > if ((!_.isNil(query.centerLon) || !_.isNil(query.longitude)) &amp;&amp;</span>
931
- <span class="cstat-no" title="statement not covered" > (!_.isNil(query.centerLat) || !_.isNil(query.latitude)) &amp;&amp; !_.isNil(query.distance)) {</span>
932
- <span class="cstat-no" title="statement not covered" > const longitude = (_.isNil(query.centerLon) ? _.toNumber(query.longitude) : _.toNumber(query.centerLon))</span>
933
- <span class="cstat-no" title="statement not covered" > const latitude = (_.isNil(query.centerLat) ? _.toNumber(query.latitude) : _.toNumber(query.centerLat))</span>
934
- <span class="cstat-no" title="statement not covered" > const distance = _.toNumber(query.distance)</span>
935
- <span class="cstat-no" title="statement not covered" > // Transform to MongoDB spatial request</span>
936
- <span class="cstat-no" title="statement not covered" > delete query.centerLon</span>
937
- <span class="cstat-no" title="statement not covered" > delete query.longitude</span>
938
- <span class="cstat-no" title="statement not covered" > delete query.centerLat</span>
939
- <span class="cstat-no" title="statement not covered" > delete query.latitude</span>
940
- <span class="cstat-no" title="statement not covered" > delete query.distance</span>
941
- <span class="cstat-no" title="statement not covered" > // Aggregation requires a specific operator</span>
942
- <span class="cstat-no" title="statement not covered" > if (query.$aggregate) {</span>
469
+ <a name='L404'></a><a href='#L404'>404</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
470
+ <span class="cline-any cline-yes">1x</span>
471
+ <span class="cline-any cline-yes">1x</span>
472
+ <span class="cline-any cline-yes">1x</span>
473
+ <span class="cline-any cline-yes">1x</span>
474
+ <span class="cline-any cline-yes">1x</span>
475
+ <span class="cline-any cline-yes">1x</span>
476
+ <span class="cline-any cline-yes">2x</span>
477
+ <span class="cline-any cline-yes">2x</span>
478
+ <span class="cline-any cline-yes">2x</span>
479
+ <span class="cline-any cline-yes">2x</span>
480
+ <span class="cline-any cline-yes">2x</span>
481
+ <span class="cline-any cline-yes">2x</span>
482
+ <span class="cline-any cline-yes">1x</span>
483
+ <span class="cline-any cline-yes">10x</span>
484
+ <span class="cline-any cline-yes">10x</span>
485
+ <span class="cline-any cline-yes">10x</span>
486
+ <span class="cline-any cline-yes">10x</span>
487
+ <span class="cline-any cline-yes">10x</span>
488
+ <span class="cline-any cline-yes">10x</span>
489
+ <span class="cline-any cline-yes">10x</span>
490
+ <span class="cline-any cline-yes">10x</span>
491
+ <span class="cline-any cline-yes">10x</span>
492
+ <span class="cline-any cline-yes">10x</span>
493
+ <span class="cline-any cline-yes">10x</span>
494
+ <span class="cline-any cline-yes">10x</span>
495
+ <span class="cline-any cline-yes">10x</span>
496
+ <span class="cline-any cline-yes">10x</span>
497
+ <span class="cline-any cline-yes">10x</span>
498
+ <span class="cline-any cline-yes">10x</span>
499
+ <span class="cline-any cline-yes">10x</span>
500
+ <span class="cline-any cline-yes">10x</span>
501
+ <span class="cline-any cline-yes">1x</span>
502
+ <span class="cline-any cline-yes">1x</span>
503
+ <span class="cline-any cline-yes">55x</span>
504
+ <span class="cline-any cline-yes">55x</span>
505
+ <span class="cline-any cline-yes">54x</span>
506
+ <span class="cline-any cline-yes">1x</span>
507
+ <span class="cline-any cline-yes">1x</span>
508
+ <span class="cline-any cline-yes">1x</span>
509
+ <span class="cline-any cline-yes">54x</span>
510
+ <span class="cline-any cline-yes">55x</span>
511
+ <span class="cline-any cline-yes">1x</span>
512
+ <span class="cline-any cline-yes">1x</span>
513
+ <span class="cline-any cline-yes">55x</span>
514
+ <span class="cline-any cline-yes">55x</span>
515
+ <span class="cline-any cline-yes">54x</span>
516
+ <span class="cline-any cline-yes">54x</span>
517
+ <span class="cline-any cline-yes">54x</span>
518
+ <span class="cline-any cline-yes">54x</span>
519
+ <span class="cline-any cline-yes">1x</span>
520
+ <span class="cline-any cline-yes">1x</span>
521
+ <span class="cline-any cline-yes">1x</span>
522
+ <span class="cline-any cline-yes">1x</span>
523
+ <span class="cline-any cline-yes">1x</span>
524
+ <span class="cline-any cline-yes">1x</span>
525
+ <span class="cline-any cline-yes">1x</span>
526
+ <span class="cline-any cline-yes">1x</span>
527
+ <span class="cline-any cline-yes">1x</span>
528
+ <span class="cline-any cline-yes">1x</span>
529
+ <span class="cline-any cline-yes">1x</span>
530
+ <span class="cline-any cline-no">&nbsp;</span>
531
+ <span class="cline-any cline-no">&nbsp;</span>
532
+ <span class="cline-any cline-no">&nbsp;</span>
533
+ <span class="cline-any cline-no">&nbsp;</span>
534
+ <span class="cline-any cline-no">&nbsp;</span>
535
+ <span class="cline-any cline-no">&nbsp;</span>
536
+ <span class="cline-any cline-yes">1x</span>
537
+ <span class="cline-any cline-yes">1x</span>
538
+ <span class="cline-any cline-yes">1x</span>
539
+ <span class="cline-any cline-yes">1x</span>
540
+ <span class="cline-any cline-yes">1x</span>
541
+ <span class="cline-any cline-yes">1x</span>
542
+ <span class="cline-any cline-yes">1x</span>
543
+ <span class="cline-any cline-yes">1x</span>
544
+ <span class="cline-any cline-yes">1x</span>
545
+ <span class="cline-any cline-yes">1x</span>
546
+ <span class="cline-any cline-yes">1x</span>
547
+ <span class="cline-any cline-yes">1x</span>
548
+ <span class="cline-any cline-yes">1x</span>
549
+ <span class="cline-any cline-yes">1x</span>
550
+ <span class="cline-any cline-yes">1x</span>
551
+ <span class="cline-any cline-yes">1x</span>
552
+ <span class="cline-any cline-yes">1x</span>
553
+ <span class="cline-any cline-yes">1x</span>
554
+ <span class="cline-any cline-yes">1x</span>
555
+ <span class="cline-any cline-yes">54x</span>
556
+ <span class="cline-any cline-yes">54x</span>
557
+ <span class="cline-any cline-yes">6x</span>
558
+ <span class="cline-any cline-yes">6x</span>
559
+ <span class="cline-any cline-yes">6x</span>
560
+ <span class="cline-any cline-yes">6x</span>
561
+ <span class="cline-any cline-yes">6x</span>
562
+ <span class="cline-any cline-yes">6x</span>
563
+ <span class="cline-any cline-yes">6x</span>
564
+ <span class="cline-any cline-yes">6x</span>
565
+ <span class="cline-any cline-yes">6x</span>
566
+ <span class="cline-any cline-yes">6x</span>
567
+ <span class="cline-any cline-yes">6x</span>
568
+ <span class="cline-any cline-yes">6x</span>
569
+ <span class="cline-any cline-yes">6x</span>
570
+ <span class="cline-any cline-yes">6x</span>
571
+ <span class="cline-any cline-yes">2x</span>
572
+ <span class="cline-any cline-yes">2x</span>
573
+ <span class="cline-any cline-yes">2x</span>
574
+ <span class="cline-any cline-yes">6x</span>
575
+ <span class="cline-any cline-yes">4x</span>
576
+ <span class="cline-any cline-yes">4x</span>
577
+ <span class="cline-any cline-yes">4x</span>
578
+ <span class="cline-any cline-yes">4x</span>
579
+ <span class="cline-any cline-yes">4x</span>
580
+ <span class="cline-any cline-yes">6x</span>
581
+ <span class="cline-any cline-yes">54x</span>
582
+ <span class="cline-any cline-yes">54x</span>
583
+ <span class="cline-any cline-yes">3x</span>
584
+ <span class="cline-any cline-yes">3x</span>
585
+ <span class="cline-any cline-yes">3x</span>
586
+ <span class="cline-any cline-yes">3x</span>
587
+ <span class="cline-any cline-yes">3x</span>
588
+ <span class="cline-any cline-yes">3x</span>
589
+ <span class="cline-any cline-yes">3x</span>
590
+ <span class="cline-any cline-yes">3x</span>
591
+ <span class="cline-any cline-yes">3x</span>
592
+ <span class="cline-any cline-yes">3x</span>
593
+ <span class="cline-any cline-yes">3x</span>
594
+ <span class="cline-any cline-yes">3x</span>
595
+ <span class="cline-any cline-yes">3x</span>
596
+ <span class="cline-any cline-yes">3x</span>
597
+ <span class="cline-any cline-yes">3x</span>
598
+ <span class="cline-any cline-yes">54x</span>
599
+ <span class="cline-any cline-yes">55x</span>
600
+ <span class="cline-any cline-yes">55x</span>
601
+ <span class="cline-any cline-yes">55x</span>
602
+ <span class="cline-any cline-yes">1x</span>
603
+ <span class="cline-any cline-yes">1x</span>
604
+ <span class="cline-any cline-yes">3x</span>
605
+ <span class="cline-any cline-yes">38x</span>
606
+ <span class="cline-any cline-yes">38x</span>
607
+ <span class="cline-any cline-yes">38x</span>
608
+ <span class="cline-any cline-yes">38x</span>
609
+ <span class="cline-any cline-yes">38x</span>
610
+ <span class="cline-any cline-yes">38x</span>
611
+ <span class="cline-any cline-yes">38x</span>
612
+ <span class="cline-any cline-yes">38x</span>
613
+ <span class="cline-any cline-yes">38x</span>
614
+ <span class="cline-any cline-yes">38x</span>
615
+ <span class="cline-any cline-yes">38x</span>
616
+ <span class="cline-any cline-yes">38x</span>
617
+ <span class="cline-any cline-yes">37x</span>
618
+ <span class="cline-any cline-yes">38x</span>
619
+ <span class="cline-any cline-yes">38x</span>
620
+ <span class="cline-any cline-yes">38x</span>
621
+ <span class="cline-any cline-yes">38x</span>
622
+ <span class="cline-any cline-yes">38x</span>
623
+ <span class="cline-any cline-yes">37x</span>
624
+ <span class="cline-any cline-yes">37x</span>
625
+ <span class="cline-any cline-yes">620x</span>
626
+ <span class="cline-any cline-yes">620x</span>
627
+ <span class="cline-any cline-yes">616x</span>
628
+ <span class="cline-any cline-yes">620x</span>
629
+ <span class="cline-any cline-yes">613x</span>
630
+ <span class="cline-any cline-yes">620x</span>
631
+ <span class="cline-any cline-yes">620x</span>
632
+ <span class="cline-any cline-yes">2x</span>
633
+ <span class="cline-any cline-yes">2x</span>
634
+ <span class="cline-any cline-yes">37x</span>
635
+ <span class="cline-any cline-yes">37x</span>
636
+ <span class="cline-any cline-yes">619x</span>
637
+ <span class="cline-any cline-yes">619x</span>
638
+ <span class="cline-any cline-yes">619x</span>
639
+ <span class="cline-any cline-yes">4x</span>
640
+ <span class="cline-any cline-yes">4x</span>
641
+ <span class="cline-any cline-no">&nbsp;</span>
642
+ <span class="cline-any cline-no">&nbsp;</span>
643
+ <span class="cline-any cline-yes">4x</span>
644
+ <span class="cline-any cline-yes">619x</span>
645
+ <span class="cline-any cline-yes">2x</span>
646
+ <span class="cline-any cline-yes">2x</span>
647
+ <span class="cline-any cline-yes">619x</span>
648
+ <span class="cline-any cline-yes">3x</span>
649
+ <span class="cline-any cline-yes">3x</span>
650
+ <span class="cline-any cline-yes">619x</span>
651
+ <span class="cline-any cline-yes">619x</span>
652
+ <span class="cline-any cline-yes">619x</span>
653
+ <span class="cline-any cline-yes">611x</span>
654
+ <span class="cline-any cline-yes">611x</span>
655
+ <span class="cline-any cline-yes">611x</span>
656
+ <span class="cline-any cline-yes">611x</span>
657
+ <span class="cline-any cline-yes">611x</span>
658
+ <span class="cline-any cline-yes">619x</span>
659
+ <span class="cline-any cline-yes">3x</span>
660
+ <span class="cline-any cline-yes">3x</span>
661
+ <span class="cline-any cline-yes">3x</span>
662
+ <span class="cline-any cline-yes">3x</span>
663
+ <span class="cline-any cline-yes">3x</span>
664
+ <span class="cline-any cline-yes">8x</span>
665
+ <span class="cline-any cline-yes">5x</span>
666
+ <span class="cline-any cline-yes">5x</span>
667
+ <span class="cline-any cline-yes">5x</span>
668
+ <span class="cline-any cline-yes">5x</span>
669
+ <span class="cline-any cline-yes">5x</span>
670
+ <span class="cline-any cline-yes">5x</span>
671
+ <span class="cline-any cline-yes">5x</span>
672
+ <span class="cline-any cline-yes">37x</span>
673
+ <span class="cline-any cline-yes">37x</span>
674
+ <span class="cline-any cline-yes">38x</span>
675
+ <span class="cline-any cline-yes">2x</span>
676
+ <span class="cline-any cline-yes">5x</span>
677
+ <span class="cline-any cline-yes">5x</span>
678
+ <span class="cline-any cline-yes">5x</span>
679
+ <span class="cline-any cline-yes">20x</span>
680
+ <span class="cline-any cline-yes">10x</span>
681
+ <span class="cline-any cline-yes">10x</span>
682
+ <span class="cline-any cline-yes">5x</span>
683
+ <span class="cline-any cline-yes">5x</span>
684
+ <span class="cline-any cline-no">&nbsp;</span>
685
+ <span class="cline-any cline-no">&nbsp;</span>
686
+ <span class="cline-any cline-no">&nbsp;</span>
687
+ <span class="cline-any cline-no">&nbsp;</span>
688
+ <span class="cline-any cline-no">&nbsp;</span>
689
+ <span class="cline-any cline-yes">2x</span>
690
+ <span class="cline-any cline-yes">2x</span>
691
+ <span class="cline-any cline-yes">37x</span>
692
+ <span class="cline-any cline-yes">38x</span>
693
+ <span class="cline-any cline-yes">36x</span>
694
+ <span class="cline-any cline-yes">36x</span>
695
+ <span class="cline-any cline-yes">36x</span>
696
+ <span class="cline-any cline-yes">36x</span>
697
+ <span class="cline-any cline-yes">36x</span>
698
+ <span class="cline-any cline-yes">38x</span>
699
+ <span class="cline-any cline-no">&nbsp;</span>
700
+ <span class="cline-any cline-no">&nbsp;</span>
701
+ <span class="cline-any cline-no">&nbsp;</span>
702
+ <span class="cline-any cline-no">&nbsp;</span>
703
+ <span class="cline-any cline-yes">1x</span>
704
+ <span class="cline-any cline-yes">1x</span>
705
+ <span class="cline-any cline-yes">1x</span>
706
+ <span class="cline-any cline-yes">38x</span>
707
+ <span class="cline-any cline-yes">3x</span>
708
+ <span class="cline-any cline-yes">1x</span>
709
+ <span class="cline-any cline-yes">1x</span>
710
+ <span class="cline-any cline-yes">36x</span>
711
+ <span class="cline-any cline-yes">36x</span>
712
+ <span class="cline-any cline-yes">36x</span>
713
+ <span class="cline-any cline-yes">36x</span>
714
+ <span class="cline-any cline-yes">36x</span>
715
+ <span class="cline-any cline-yes">8x</span>
716
+ <span class="cline-any cline-yes">8x</span>
717
+ <span class="cline-any cline-yes">8x</span>
718
+ <span class="cline-any cline-yes">8x</span>
719
+ <span class="cline-any cline-yes">8x</span>
720
+ <span class="cline-any cline-yes">8x</span>
721
+ <span class="cline-any cline-yes">8x</span>
722
+ <span class="cline-any cline-yes">8x</span>
723
+ <span class="cline-any cline-yes">8x</span>
724
+ <span class="cline-any cline-yes">8x</span>
725
+ <span class="cline-any cline-yes">8x</span>
726
+ <span class="cline-any cline-yes">8x</span>
727
+ <span class="cline-any cline-yes">8x</span>
728
+ <span class="cline-any cline-yes">8x</span>
729
+ <span class="cline-any cline-yes">8x</span>
730
+ <span class="cline-any cline-yes">8x</span>
731
+ <span class="cline-any cline-yes">3x</span>
732
+ <span class="cline-any cline-yes">3x</span>
733
+ <span class="cline-any cline-yes">3x</span>
734
+ <span class="cline-any cline-yes">3x</span>
735
+ <span class="cline-any cline-yes">3x</span>
736
+ <span class="cline-any cline-yes">3x</span>
737
+ <span class="cline-any cline-yes">3x</span>
738
+ <span class="cline-any cline-yes">8x</span>
739
+ <span class="cline-any cline-yes">5x</span>
740
+ <span class="cline-any cline-yes">5x</span>
741
+ <span class="cline-any cline-yes">5x</span>
742
+ <span class="cline-any cline-yes">5x</span>
743
+ <span class="cline-any cline-yes">5x</span>
744
+ <span class="cline-any cline-yes">5x</span>
745
+ <span class="cline-any cline-yes">5x</span>
746
+ <span class="cline-any cline-yes">5x</span>
747
+ <span class="cline-any cline-yes">5x</span>
748
+ <span class="cline-any cline-yes">5x</span>
749
+ <span class="cline-any cline-yes">5x</span>
750
+ <span class="cline-any cline-yes">5x</span>
751
+ <span class="cline-any cline-yes">5x</span>
752
+ <span class="cline-any cline-yes">5x</span>
753
+ <span class="cline-any cline-yes">3x</span>
754
+ <span class="cline-any cline-yes">3x</span>
755
+ <span class="cline-any cline-yes">3x</span>
756
+ <span class="cline-any cline-yes">3x</span>
757
+ <span class="cline-any cline-yes">5x</span>
758
+ <span class="cline-any cline-yes">8x</span>
759
+ <span class="cline-any cline-yes">8x</span>
760
+ <span class="cline-any cline-yes">8x</span>
761
+ <span class="cline-any cline-yes">8x</span>
762
+ <span class="cline-any cline-yes">8x</span>
763
+ <span class="cline-any cline-yes">8x</span>
764
+ <span class="cline-any cline-yes">8x</span>
765
+ <span class="cline-any cline-yes">8x</span>
766
+ <span class="cline-any cline-yes">8x</span>
767
+ <span class="cline-any cline-yes">8x</span>
768
+ <span class="cline-any cline-yes">7x</span>
769
+ <span class="cline-any cline-yes">7x</span>
770
+ <span class="cline-any cline-yes">8x</span>
771
+ <span class="cline-any cline-yes">8x</span>
772
+ <span class="cline-any cline-yes">8x</span>
773
+ <span class="cline-any cline-yes">8x</span>
774
+ <span class="cline-any cline-yes">8x</span>
775
+ <span class="cline-any cline-yes">8x</span>
776
+ <span class="cline-any cline-yes">8x</span>
777
+ <span class="cline-any cline-yes">8x</span>
778
+ <span class="cline-any cline-yes">8x</span>
779
+ <span class="cline-any cline-yes">8x</span>
780
+ <span class="cline-any cline-yes">11x</span>
781
+ <span class="cline-any cline-yes">11x</span>
782
+ <span class="cline-any cline-yes">11x</span>
783
+ <span class="cline-any cline-yes">11x</span>
784
+ <span class="cline-any cline-yes">11x</span>
785
+ <span class="cline-any cline-yes">11x</span>
786
+ <span class="cline-any cline-no">&nbsp;</span>
787
+ <span class="cline-any cline-no">&nbsp;</span>
788
+ <span class="cline-any cline-yes">11x</span>
789
+ <span class="cline-any cline-yes">11x</span>
790
+ <span class="cline-any cline-yes">11x</span>
791
+ <span class="cline-any cline-yes">11x</span>
792
+ <span class="cline-any cline-yes">11x</span>
793
+ <span class="cline-any cline-yes">11x</span>
794
+ <span class="cline-any cline-yes">11x</span>
795
+ <span class="cline-any cline-yes">4x</span>
796
+ <span class="cline-any cline-yes">4x</span>
797
+ <span class="cline-any cline-yes">4x</span>
798
+ <span class="cline-any cline-yes">4x</span>
799
+ <span class="cline-any cline-yes">1x</span>
800
+ <span class="cline-any cline-yes">1x</span>
801
+ <span class="cline-any cline-yes">1x</span>
802
+ <span class="cline-any cline-yes">1x</span>
803
+ <span class="cline-any cline-yes">4x</span>
804
+ <span class="cline-any cline-yes">11x</span>
805
+ <span class="cline-any cline-yes">7x</span>
806
+ <span class="cline-any cline-yes">7x</span>
807
+ <span class="cline-any cline-yes">11x</span>
808
+ <span class="cline-any cline-yes">11x</span>
809
+ <span class="cline-any cline-yes">38x</span>
810
+ <span class="cline-any cline-yes">11x</span>
811
+ <span class="cline-any cline-yes">11x</span>
812
+ <span class="cline-any cline-yes">11x</span>
813
+ <span class="cline-any cline-yes">11x</span>
814
+ <span class="cline-any cline-yes">11x</span>
815
+ <span class="cline-any cline-yes">11x</span>
816
+ <span class="cline-any cline-yes">11x</span>
817
+ <span class="cline-any cline-yes">13x</span>
818
+ <span class="cline-any cline-yes">13x</span>
819
+ <span class="cline-any cline-yes">13x</span>
820
+ <span class="cline-any cline-yes">6x</span>
821
+ <span class="cline-any cline-yes">6x</span>
822
+ <span class="cline-any cline-yes">6x</span>
823
+ <span class="cline-any cline-yes">6x</span>
824
+ <span class="cline-any cline-yes">6x</span>
825
+ <span class="cline-any cline-yes">6x</span>
826
+ <span class="cline-any cline-yes">11x</span>
827
+ <span class="cline-any cline-yes">11x</span>
828
+ <span class="cline-any cline-yes">11x</span>
829
+ <span class="cline-any cline-yes">8x</span>
830
+ <span class="cline-any cline-yes">8x</span>
831
+ <span class="cline-any cline-yes">9x</span>
832
+ <span class="cline-any cline-yes">9x</span>
833
+ <span class="cline-any cline-yes">9x</span>
834
+ <span class="cline-any cline-yes">9x</span>
835
+ <span class="cline-any cline-yes">8x</span>
836
+ <span class="cline-any cline-yes">11x</span>
837
+ <span class="cline-any cline-yes">3x</span>
838
+ <span class="cline-any cline-yes">4x</span>
839
+ <span class="cline-any cline-yes">4x</span>
840
+ <span class="cline-any cline-yes">4x</span>
841
+ <span class="cline-any cline-yes">4x</span>
842
+ <span class="cline-any cline-yes">4x</span>
843
+ <span class="cline-any cline-yes">4x</span>
844
+ <span class="cline-any cline-yes">4x</span>
845
+ <span class="cline-any cline-yes">4x</span>
846
+ <span class="cline-any cline-yes">4x</span>
847
+ <span class="cline-any cline-yes">4x</span>
848
+ <span class="cline-any cline-yes">4x</span>
849
+ <span class="cline-any cline-yes">4x</span>
850
+ <span class="cline-any cline-yes">4x</span>
851
+ <span class="cline-any cline-yes">4x</span>
852
+ <span class="cline-any cline-yes">3x</span>
853
+ <span class="cline-any cline-no">&nbsp;</span>
854
+ <span class="cline-any cline-yes">3x</span>
855
+ <span class="cline-any cline-yes">4x</span>
856
+ <span class="cline-any cline-yes">4x</span>
857
+ <span class="cline-any cline-no">&nbsp;</span>
858
+ <span class="cline-any cline-no">&nbsp;</span>
859
+ <span class="cline-any cline-no">&nbsp;</span>
860
+ <span class="cline-any cline-yes">3x</span>
861
+ <span class="cline-any cline-yes">3x</span>
862
+ <span class="cline-any cline-yes">8x</span>
863
+ <span class="cline-any cline-yes">8x</span>
864
+ <span class="cline-any cline-yes">8x</span>
865
+ <span class="cline-any cline-yes">8x</span>
866
+ <span class="cline-any cline-yes">8x</span>
867
+ <span class="cline-any cline-yes">8x</span>
868
+ <span class="cline-any cline-yes">8x</span>
869
+ <span class="cline-any cline-yes">8x</span>
870
+ <span class="cline-any cline-yes">36x</span>
871
+ <span class="cline-any cline-yes">36x</span>
872
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import _ from 'lodash'
873
+ import { marshallGeometry } from '../marshall.js'
874
+ import makeDebug from 'debug'
875
+ &nbsp;
876
+ const debug = makeDebug('kdk:map:query:hooks')
877
+ &nbsp;
878
+ export function marshallGeometryQuery (hook) {
879
+ const query = hook.params.query
880
+ if (_.isNil(query)) <span class="branch-0 cbranch-no" title="branch not covered" >return</span>
881
+ const geometry = query.geometry
882
+ if (_.isNil(geometry)) <span class="branch-0 cbranch-no" title="branch not covered" >return</span>
883
+ marshallGeometry(geometry)
884
+ }
885
+ &nbsp;
886
+ function getGeometryQueryForBBox (bbox) {
887
+ // Here we use the custom MongoDB CRS that enforces counter-clockwise winding order
888
+ // and allows to support queries with a single-ringed GeoJSON polygon
889
+ // whose area is greater than or equal to a single hemisphere.
890
+ // Otherwise $geoIntersects queries for the complementary geometry.
891
+ return {
892
+ $geoIntersects: {
893
+ $geometry: {
894
+ type: 'Polygon',
895
+ coordinates: [bbox],
896
+ crs: {
897
+ type: 'name',
898
+ properties: { name: 'urn:x-mongodb:crs:strictwinding:EPSG:4326' }
899
+ }
900
+ }
901
+ }
902
+ }
903
+ }
904
+ &nbsp;
905
+ export function marshallGeoJsonQuery (hook) {
906
+ const query = hook.params.query
907
+ if (query) {
908
+ if (query.geoJson) {
909
+ delete query.geoJson
910
+ hook.params.asGeoJson = true
911
+ }
912
+ }
913
+ }
914
+ &nbsp;
915
+ export function marshallSpatialQuery (hook) {
916
+ const query = hook.params.query
917
+ if (query) {
918
+ if (!_.isNil(query.geometry)) marshallGeometry(query.geometry)
919
+ // Shortcut for proximity query
920
+ if ((!_.isNil(query.centerLon) || !_.isNil(query.longitude)) &amp;&amp;
921
+ (!_.isNil(query.centerLat) || !_.isNil(query.latitude)) &amp;&amp; !_.isNil(query.distance)) {
922
+ const longitude = (_.isNil(query.centerLon) ? _.toNumber(query.longitude) <span class="branch-0 cbranch-no" title="branch not covered" >: _.toNumber(query.centerLon))</span>
923
+ const latitude = (_.isNil(query.centerLat) ? _.toNumber(query.latitude) <span class="branch-0 cbranch-no" title="branch not covered" >: _.toNumber(query.centerLat))</span>
924
+ const distance = _.toNumber(query.distance)
925
+ // Transform to MongoDB spatial request
926
+ delete query.centerLon
927
+ delete query.longitude
928
+ delete query.centerLat
929
+ delete query.latitude
930
+ delete query.distance
931
+ // Aggregation requires a specific operator
932
+ if (query.$aggregate) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
943
933
  <span class="cstat-no" title="statement not covered" > query.$geoNear = {</span>
944
934
  <span class="cstat-no" title="statement not covered" > near: { type: 'Point', coordinates: [longitude, latitude] },</span>
945
935
  <span class="cstat-no" title="statement not covered" > maxDistance: distance,</span>
946
936
  <span class="cstat-no" title="statement not covered" > distanceField: 'distance',</span>
947
937
  <span class="cstat-no" title="statement not covered" > spherical: true</span>
948
938
  <span class="cstat-no" title="statement not covered" > }</span>
949
- <span class="cstat-no" title="statement not covered" > } else {</span>
950
- <span class="cstat-no" title="statement not covered" > /* We switched from $near to $geoWithin due to https://github.com/kalisio/kdk/issues/345</span>
951
- <span class="cstat-no" title="statement not covered" > query.geometry = {</span>
952
- <span class="cstat-no" title="statement not covered" > $near: {</span>
953
- <span class="cstat-no" title="statement not covered" > $geometry: {</span>
954
- <span class="cstat-no" title="statement not covered" > type: 'Point',</span>
955
- <span class="cstat-no" title="statement not covered" > coordinates: [longitude, latitude]</span>
956
- <span class="cstat-no" title="statement not covered" > },</span>
957
- <span class="cstat-no" title="statement not covered" > $maxDistance: distance</span>
958
- <span class="cstat-no" title="statement not covered" > }</span>
959
- <span class="cstat-no" title="statement not covered" > }</span>
960
- <span class="cstat-no" title="statement not covered" > */</span>
961
- <span class="cstat-no" title="statement not covered" > query.geometry = {</span>
962
- <span class="cstat-no" title="statement not covered" > $geoWithin: {</span>
963
- <span class="cstat-no" title="statement not covered" > $centerSphere: [[longitude, latitude], distance / 6378137.0] // Earth radius as in radians</span>
964
- <span class="cstat-no" title="statement not covered" > }</span>
965
- <span class="cstat-no" title="statement not covered" > }</span>
966
- <span class="cstat-no" title="statement not covered" > }</span>
967
- <span class="cstat-no" title="statement not covered" > }</span>
968
- <span class="cstat-no" title="statement not covered" > // Shortcut for bbox query</span>
969
- <span class="cstat-no" title="statement not covered" > if (!_.isNil(query.south) &amp;&amp; !_.isNil(query.north) &amp;&amp; !_.isNil(query.west) &amp;&amp; !_.isNil(query.east)) {</span>
970
- <span class="cstat-no" title="statement not covered" > const south = _.toNumber(query.south)</span>
971
- <span class="cstat-no" title="statement not covered" > const north = _.toNumber(query.north)</span>
972
- <span class="cstat-no" title="statement not covered" > const west = _.toNumber(query.west)</span>
973
- <span class="cstat-no" title="statement not covered" > const east = _.toNumber(query.east)</span>
974
- <span class="cstat-no" title="statement not covered" > // Transform to MongoDB spatial request</span>
975
- <span class="cstat-no" title="statement not covered" > delete query.south</span>
976
- <span class="cstat-no" title="statement not covered" > delete query.north</span>
977
- <span class="cstat-no" title="statement not covered" > delete query.west</span>
978
- <span class="cstat-no" title="statement not covered" > delete query.east</span>
979
- <span class="cstat-no" title="statement not covered" > </span>
980
- <span class="cstat-no" title="statement not covered" > // FIXME: MongoDB should allow to support queries with a single-ringed GeoJSON polygon</span>
981
- <span class="cstat-no" title="statement not covered" > // whose area is greater than or equal to a single hemisphere.</span>
982
- <span class="cstat-no" title="statement not covered" > // However, we did not succeeed in making it work as expected, for now on we split large polygon into two halfs.</span>
983
- <span class="cstat-no" title="statement not covered" > if ((east-west) &lt;= 180) {</span>
984
- <span class="cstat-no" title="statement not covered" > // BBox as a polygon also requires the closing point.</span>
985
- <span class="cstat-no" title="statement not covered" > const bbox = [[west, south], [east, south], [east, north], [west, north], [west, south]]</span>
986
- <span class="cstat-no" title="statement not covered" > query.geometry = getGeometryQueryForBBox(bbox)</span>
987
- <span class="cstat-no" title="statement not covered" > } else {</span>
988
- <span class="cstat-no" title="statement not covered" > // BBox as a polygon also requires the closing point.</span>
989
- <span class="cstat-no" title="statement not covered" > const leftHalfBbox = [[west, south], [0.5*(west+east), south], [0.5*(west+east), north], [west, north], [west, south]]</span>
990
- <span class="cstat-no" title="statement not covered" > const rightHalfBbox = [[0.5*(west+east), south], [east, south], [east, north], [0.5*(west+east), north], [0.5*(west+east), south]]</span>
991
- <span class="cstat-no" title="statement not covered" > query.$or = [{ geometry: getGeometryQueryForBBox(leftHalfBbox) }, { geometry: getGeometryQueryForBBox(rightHalfBbox) }]</span>
992
- <span class="cstat-no" title="statement not covered" > }</span>
993
- <span class="cstat-no" title="statement not covered" > }</span>
994
- <span class="cstat-no" title="statement not covered" > // Shortcut for location query</span>
995
- <span class="cstat-no" title="statement not covered" > if (!_.isNil(query.longitude) &amp;&amp; !_.isNil(query.latitude)) {</span>
996
- <span class="cstat-no" title="statement not covered" > const longitude = _.toNumber(query.longitude)</span>
997
- <span class="cstat-no" title="statement not covered" > const latitude = _.toNumber(query.latitude)</span>
998
- <span class="cstat-no" title="statement not covered" > // Transform to MongoDB spatial request</span>
999
- <span class="cstat-no" title="statement not covered" > delete query.longitude</span>
1000
- <span class="cstat-no" title="statement not covered" > delete query.latitude</span>
1001
- <span class="cstat-no" title="statement not covered" > const geometryQuery = {</span>
1002
- <span class="cstat-no" title="statement not covered" > $geoIntersects: {</span>
1003
- <span class="cstat-no" title="statement not covered" > $geometry: {</span>
1004
- <span class="cstat-no" title="statement not covered" > type: 'Point',</span>
1005
- <span class="cstat-no" title="statement not covered" > coordinates: [longitude, latitude]</span>
1006
- <span class="cstat-no" title="statement not covered" > }</span>
1007
- <span class="cstat-no" title="statement not covered" > }</span>
1008
- <span class="cstat-no" title="statement not covered" > }</span>
1009
- <span class="cstat-no" title="statement not covered" > query.geometry = geometryQuery</span>
1010
- <span class="cstat-no" title="statement not covered" > }</span>
1011
- <span class="cstat-no" title="statement not covered" > }</span>
1012
- <span class="cstat-no" title="statement not covered" > // Include GeoJson query by default</span>
1013
- <span class="cstat-no" title="statement not covered" > marshallGeoJsonQuery(hook)</span>
1014
- <span class="cstat-no" title="statement not covered" >}</span>
1015
- <span class="cstat-no" title="statement not covered" ></span>
1016
- <span class="cstat-no" title="statement not covered" >export function asGeoJson (options = {}) {</span>
1017
- <span class="cstat-no" title="statement not covered" > return function (hook) {</span>
1018
- <span class="cstat-no" title="statement not covered" > const params = hook.params</span>
1019
- <span class="cstat-no" title="statement not covered" > const query = params.query</span>
1020
- <span class="cstat-no" title="statement not covered" > if (!options.force &amp;&amp; !params.asGeoJson) return</span>
1021
- <span class="cstat-no" title="statement not covered" > if (_.has(query, '$distinct') || _.has(query, '$aggregation')) return // Not applicable in this case</span>
1022
- <span class="cstat-no" title="statement not covered" > const longitudeProperty = (options.longitudeProperty || 'longitude')</span>
1023
- <span class="cstat-no" title="statement not covered" > const latitudeProperty = (options.latitudeProperty || 'latitude')</span>
1024
- <span class="cstat-no" title="statement not covered" > const altitudeProperty = (options.altitudeProperty || 'altitude')</span>
1025
- <span class="cstat-no" title="statement not covered" > const geometryProperty = (options.geometryProperty || 'geometry')</span>
1026
- <span class="cstat-no" title="statement not covered" > const allowNullGeometries = _.get(options, 'allowNullGeometries', false)</span>
1027
- <span class="cstat-no" title="statement not covered" > let results = _.get(hook, options.dataPath || 'result')</span>
1028
- <span class="cstat-no" title="statement not covered" > // Already as GeoJson ?</span>
1029
- <span class="cstat-no" title="statement not covered" > if (results.type === 'FeatureCollection') return</span>
1030
- <span class="cstat-no" title="statement not covered" > const isPaginated = !_.isNil(results.data)</span>
1031
- <span class="cstat-no" title="statement not covered" > const pagination = (isPaginated ? _.pick(results, ['total', 'skip', 'limit']) : {})</span>
1032
- <span class="cstat-no" title="statement not covered" > results = (isPaginated ? results.data : results)</span>
1033
- <span class="cstat-no" title="statement not covered" > // Single item case, i.e. GET ?</span>
1034
- <span class="cstat-no" title="statement not covered" > const isFeatureCollection = Array.isArray(results)</span>
1035
- <span class="cstat-no" title="statement not covered" > if (!isFeatureCollection) results = [results]</span>
1036
- <span class="cstat-no" title="statement not covered" > results = results</span>
1037
- <span class="cstat-no" title="statement not covered" > .filter(item =&gt; {</span>
1038
- <span class="cstat-no" title="statement not covered" > // Check if item is not already in GeoJson feature format and we can convert if required</span>
1039
- <span class="cstat-no" title="statement not covered" > return (_.has(item, longitudeProperty) &amp;&amp; _.has(item, latitudeProperty)) ||</span>
1040
- <span class="cstat-no" title="statement not covered" > // When performing feature aggregation on geometries the result can be an array</span>
1041
- <span class="cstat-no" title="statement not covered" > Array.isArray(_.get(item, geometryProperty)) ||</span>
1042
- <span class="cstat-no" title="statement not covered" > // Check for a geometry property (previously provided or already transformed item)</span>
1043
- <span class="cstat-no" title="statement not covered" > (_.has(item, geometryProperty + '.type') &amp;&amp; _.has(item, geometryProperty + '.coordinates')) ||</span>
1044
- <span class="cstat-no" title="statement not covered" > (_.has(item, geometryProperty + '.geometry.type') &amp;&amp; _.has(item, geometryProperty + '.geometry.coordinates')) ||</span>
1045
- <span class="cstat-no" title="statement not covered" > // Check for null geometries when allowed</span>
1046
- <span class="cstat-no" title="statement not covered" > allowNullGeometries</span>
1047
- <span class="cstat-no" title="statement not covered" > })</span>
1048
- <span class="cstat-no" title="statement not covered" > .map(item =&gt; {</span>
1049
- <span class="cstat-no" title="statement not covered" > let coordinates</span>
1050
- <span class="cstat-no" title="statement not covered" > // Keep track of coordinates before picking properties</span>
1051
- <span class="cstat-no" title="statement not covered" > if (_.has(item, longitudeProperty) &amp;&amp; _.has(item, latitudeProperty)) {</span>
1052
- <span class="cstat-no" title="statement not covered" > coordinates = [_.get(item, longitudeProperty), _.get(item, latitudeProperty)]</span>
1053
- <span class="cstat-no" title="statement not covered" > if (_.has(item, altitudeProperty)) {</span>
939
+ } else {
940
+ /* We switched from $near to $geoWithin due to https://github.com/kalisio/kdk/issues/345
941
+ query.geometry = {
942
+ $near: {
943
+ $geometry: {
944
+ type: 'Point',
945
+ coordinates: [longitude, latitude]
946
+ },
947
+ $maxDistance: distance
948
+ }
949
+ }
950
+ */
951
+ query.geometry = {
952
+ $geoWithin: {
953
+ $centerSphere: [[longitude, latitude], distance / 6378137.0] // Earth radius as in radians
954
+ }
955
+ }
956
+ }
957
+ }
958
+ // Shortcut for bbox query
959
+ if (!_.isNil(query.south) &amp;&amp; !_.isNil(query.north) &amp;&amp; !_.isNil(query.west) &amp;&amp; !_.isNil(query.east)) {
960
+ const south = _.toNumber(query.south)
961
+ const north = _.toNumber(query.north)
962
+ const west = _.toNumber(query.west)
963
+ const east = _.toNumber(query.east)
964
+ // Transform to MongoDB spatial request
965
+ delete query.south
966
+ delete query.north
967
+ delete query.west
968
+ delete query.east
969
+
970
+ // FIXME: MongoDB should allow to support queries with a single-ringed GeoJSON polygon
971
+ // whose area is greater than or equal to a single hemisphere.
972
+ // However, we did not succeeed in making it work as expected, for now on we split large polygon into two halfs.
973
+ if ((east-west) &lt;= 180) {
974
+ // BBox as a polygon also requires the closing point.
975
+ const bbox = [[west, south], [east, south], [east, north], [west, north], [west, south]]
976
+ query.geometry = getGeometryQueryForBBox(bbox)
977
+ } else {
978
+ // BBox as a polygon also requires the closing point.
979
+ const leftHalfBbox = [[west, south], [0.5*(west+east), south], [0.5*(west+east), north], [west, north], [west, south]]
980
+ const rightHalfBbox = [[0.5*(west+east), south], [east, south], [east, north], [0.5*(west+east), north], [0.5*(west+east), south]]
981
+ query.$or = [{ geometry: getGeometryQueryForBBox(leftHalfBbox) }, { geometry: getGeometryQueryForBBox(rightHalfBbox) }]
982
+ }
983
+ }
984
+ // Shortcut for location query
985
+ if (!_.isNil(query.longitude) &amp;&amp; !_.isNil(query.latitude)) {
986
+ const longitude = _.toNumber(query.longitude)
987
+ const latitude = _.toNumber(query.latitude)
988
+ // Transform to MongoDB spatial request
989
+ delete query.longitude
990
+ delete query.latitude
991
+ const geometryQuery = {
992
+ $geoIntersects: {
993
+ $geometry: {
994
+ type: 'Point',
995
+ coordinates: [longitude, latitude]
996
+ }
997
+ }
998
+ }
999
+ query.geometry = geometryQuery
1000
+ }
1001
+ }
1002
+ // Include GeoJson query by default
1003
+ marshallGeoJsonQuery(hook)
1004
+ }
1005
+ &nbsp;
1006
+ export function asGeoJson (options = {}) {
1007
+ return function (hook) {
1008
+ const params = hook.params
1009
+ const query = params.query
1010
+ if (!options.force &amp;&amp; !params.asGeoJson) <span class="branch-0 cbranch-no" title="branch not covered" >return</span>
1011
+ if (_.has(query, '$distinct') || _.has(query, '$aggregation')) return // Not applicable in this case
1012
+ const longitudeProperty = (options.longitudeProperty || 'longitude')
1013
+ const latitudeProperty = (options.latitudeProperty || 'latitude')
1014
+ const altitudeProperty = (options.altitudeProperty || 'altitude')
1015
+ const geometryProperty = (options.geometryProperty || 'geometry')
1016
+ const allowNullGeometries = _.get(options, 'allowNullGeometries', false)
1017
+ let results = _.get(hook, options.dataPath || 'result')
1018
+ // Already as GeoJson ?
1019
+ if (results.type === 'FeatureCollection') <span class="branch-0 cbranch-no" title="branch not covered" >return</span>
1020
+ const isPaginated = !_.isNil(results.data)
1021
+ const pagination = (isPaginated ? _.pick(results, ['total', 'skip', 'limit']) : {})
1022
+ results = (isPaginated ? results.data : results)
1023
+ // Single item case, i.e. GET ?
1024
+ const isFeatureCollection = Array.isArray(results)
1025
+ if (!isFeatureCollection) <span class="branch-0 cbranch-no" title="branch not covered" >results = [results]</span>
1026
+ results = results
1027
+ .filter(item =&gt; {
1028
+ // Check if item is not already in GeoJson feature format and we can convert if required
1029
+ return (_.has(item, longitudeProperty) &amp;&amp; _.has(item, latitudeProperty)) ||
1030
+ // When performing feature aggregation on geometries the result can be an array
1031
+ Array.isArray(_.get(item, geometryProperty)) ||
1032
+ // Check for a geometry property (previously provided or already transformed item)
1033
+ (_.has(item, geometryProperty + '.type') &amp;&amp; _.has(item, geometryProperty + '.coordinates')) ||
1034
+ (_.has(item, geometryProperty + '.geometry.type') <span class="branch-0 cbranch-no" title="branch not covered" >&amp;&amp; _.has(item, geometryProperty + '.geometry.coordinates'))</span> ||
1035
+ // Check for null geometries when allowed
1036
+ allowNullGeometries
1037
+ })
1038
+ .map(item =&gt; {
1039
+ let coordinates
1040
+ // Keep track of coordinates before picking properties
1041
+ if (_.has(item, longitudeProperty) &amp;&amp; _.has(item, latitudeProperty)) {
1042
+ coordinates = [_.get(item, longitudeProperty), _.get(item, latitudeProperty)]
1043
+ if (_.has(item, altitudeProperty)) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
1054
1044
  <span class="cstat-no" title="statement not covered" > coordinates.push(_.get(item, altitudeProperty))</span>
1055
1045
  <span class="cstat-no" title="statement not covered" > }</span>
1056
- <span class="cstat-no" title="statement not covered" > }</span>
1057
- <span class="cstat-no" title="statement not covered" > if (options.pick) {</span>
1058
- <span class="cstat-no" title="statement not covered" > item = _.pick(item, options.pick)</span>
1059
- <span class="cstat-no" title="statement not covered" > }</span>
1060
- <span class="cstat-no" title="statement not covered" > if (options.omit) {</span>
1061
- <span class="cstat-no" title="statement not covered" > item = _.omit(item, options.omit)</span>
1062
- <span class="cstat-no" title="statement not covered" > }</span>
1063
- <span class="cstat-no" title="statement not covered" > // Item locations are already in GeoJson format</span>
1064
- <span class="cstat-no" title="statement not covered" > if ((_.has(item, geometryProperty + '.type') &amp;&amp; _.has(item, geometryProperty + '.coordinates')) ||</span>
1065
- <span class="cstat-no" title="statement not covered" > (_.has(item, geometryProperty + '.geometry.type') &amp;&amp; _.has(item, geometryProperty + '.geometry.coordinates'))) {</span>
1066
- <span class="cstat-no" title="statement not covered" > return Object.assign({</span>
1067
- <span class="cstat-no" title="statement not covered" > type: 'Feature',</span>
1068
- <span class="cstat-no" title="statement not covered" > geometry: _.get(item, geometryProperty + '.geometry', _.get(item, geometryProperty)),</span>
1069
- <span class="cstat-no" title="statement not covered" > properties: {}</span>
1070
- <span class="cstat-no" title="statement not covered" > }, _.omit(item, [geometryProperty]))</span>
1071
- <span class="cstat-no" title="statement not covered" > } else if (Array.isArray(_.get(item, geometryProperty))) {</span>
1072
- <span class="cstat-no" title="statement not covered" > return Object.assign({</span>
1073
- <span class="cstat-no" title="statement not covered" > type: 'Feature',</span>
1074
- <span class="cstat-no" title="statement not covered" > geometry: { type: 'GeometryCollection', geometries: _.get(item, geometryProperty) },</span>
1075
- <span class="cstat-no" title="statement not covered" > properties: {}</span>
1076
- <span class="cstat-no" title="statement not covered" > }, _.omit(item, [geometryProperty]))</span>
1077
- <span class="cstat-no" title="statement not covered" > } else {</span>
1078
- <span class="cstat-no" title="statement not covered" > // Item locations are not already in GeoJson feature format so we need to convert</span>
1079
- <span class="cstat-no" title="statement not covered" > return Object.assign({</span>
1080
- <span class="cstat-no" title="statement not covered" > type: 'Feature',</span>
1081
- <span class="cstat-no" title="statement not covered" > geometry: (coordinates ? { type: 'Point', coordinates } : null),</span>
1082
- <span class="cstat-no" title="statement not covered" > properties: {}</span>
1083
- <span class="cstat-no" title="statement not covered" > }, _.omit(item, [longitudeProperty, latitudeProperty]))</span>
1084
- <span class="cstat-no" title="statement not covered" > }</span>
1085
- <span class="cstat-no" title="statement not covered" > })</span>
1086
- <span class="cstat-no" title="statement not covered" > // Move some data to properties ?</span>
1087
- <span class="cstat-no" title="statement not covered" > if (options.properties) {</span>
1088
- <span class="cstat-no" title="statement not covered" > results.forEach(item =&gt; {</span>
1089
- <span class="cstat-no" title="statement not covered" > // True indicates to move all fields to properties</span>
1090
- <span class="cstat-no" title="statement not covered" > if (options.properties === true) {</span>
1091
- <span class="cstat-no" title="statement not covered" > _.forOwn(item, (value, key) =&gt; {</span>
1092
- <span class="cstat-no" title="statement not covered" > if ((key === 'geometry') || (key === 'type') || (key === '_id')) return</span>
1093
- <span class="cstat-no" title="statement not covered" > _.set(item, `properties.${key}`, _.get(item, key))</span>
1094
- <span class="cstat-no" title="statement not covered" > _.unset(item, key)</span>
1095
- <span class="cstat-no" title="statement not covered" > })</span>
1096
- <span class="cstat-no" title="statement not covered" > } else { // Else we expect a specific mapping</span>
1046
+ }
1047
+ if (options.pick) {
1048
+ item = _.pick(item, options.pick)
1049
+ }
1050
+ if (options.omit) {
1051
+ item = _.omit(item, options.omit)
1052
+ }
1053
+ // Item locations are already in GeoJson format
1054
+ if ((_.has(item, geometryProperty + '.type') &amp;&amp; _.has(item, geometryProperty + '.coordinates')) ||
1055
+ (_.has(item, geometryProperty + '.geometry.type') <span class="branch-0 cbranch-no" title="branch not covered" >&amp;&amp; _.has(item, geometryProperty + '.geometry.coordinates'))</span>) {
1056
+ return Object.assign({
1057
+ type: 'Feature',
1058
+ geometry: _.get(item, geometryProperty + '.geometry', _.get(item, geometryProperty)),
1059
+ properties: {}
1060
+ }, _.omit(item, [geometryProperty]))
1061
+ } else if (Array.isArray(_.get(item, geometryProperty))) {
1062
+ return Object.assign({
1063
+ type: 'Feature',
1064
+ geometry: { type: 'GeometryCollection', geometries: _.get(item, geometryProperty) },
1065
+ properties: {}
1066
+ }, _.omit(item, [geometryProperty]))
1067
+ } else {
1068
+ // Item locations are not already in GeoJson feature format so we need to convert
1069
+ return Object.assign({
1070
+ type: 'Feature',
1071
+ geometry: (coordinates ? { type: 'Point', coordinates } : null),
1072
+ properties: {}
1073
+ }, _.omit(item, [longitudeProperty, latitudeProperty]))
1074
+ }
1075
+ })
1076
+ // Move some data to properties ?
1077
+ if (options.properties) {
1078
+ results.forEach(item =&gt; {
1079
+ // True indicates to move all fields to properties
1080
+ if (options.properties === true) {
1081
+ _.forOwn(item, (value, key) =&gt; {
1082
+ if ((key === 'geometry') || (key === 'type') || (key === '_id')) return
1083
+ _.set(item, `properties.${key}`, _.get(item, key))
1084
+ _.unset(item, key)
1085
+ })
1086
+ }<span class="branch-0 cbranch-no" title="branch not covered" > else { // Else we expect a specific mapping</span>
1097
1087
  <span class="cstat-no" title="statement not covered" > options.properties.forEach(mapping =&gt; {</span>
1098
1088
  <span class="cstat-no" title="statement not covered" > if (mapping.from) _.set(item, `properties.${mapping.to || mapping.from}`, _.get(item, `${mapping.from}`))</span>
1099
1089
  <span class="cstat-no" title="statement not covered" > if (mapping.delete) _.unset(item, `${mapping.from}`)</span>
1100
1090
  <span class="cstat-no" title="statement not covered" > })</span>
1101
1091
  <span class="cstat-no" title="statement not covered" > }</span>
1102
- <span class="cstat-no" title="statement not covered" > })</span>
1103
- <span class="cstat-no" title="statement not covered" > }</span>
1104
- <span class="cstat-no" title="statement not covered" > // If we should make it available as a GeoJson feature collection create it</span>
1105
- <span class="cstat-no" title="statement not covered" > if (isFeatureCollection &amp;&amp; _.get(options, 'asFeatureCollection', true)) {</span>
1106
- <span class="cstat-no" title="statement not covered" > // Copy pagination information if any so that client can use it anyway</span>
1107
- <span class="cstat-no" title="statement not covered" > _.set(hook, options.dataPath || 'result', Object.assign({</span>
1108
- <span class="cstat-no" title="statement not covered" > type: 'FeatureCollection',</span>
1109
- <span class="cstat-no" title="statement not covered" > features: results</span>
1110
- <span class="cstat-no" title="statement not covered" > }, pagination)) // If no pagination this merged object will be empty</span>
1111
- <span class="cstat-no" title="statement not covered" > } else if (isPaginated) {</span>
1092
+ })
1093
+ }
1094
+ // If we should make it available as a GeoJson feature collection create it
1095
+ if (isFeatureCollection &amp;&amp; _.get(options, 'asFeatureCollection', true)) {
1096
+ // Copy pagination information if any so that client can use it anyway
1097
+ _.set(hook, options.dataPath || 'result', Object.assign({
1098
+ type: 'FeatureCollection',
1099
+ features: results
1100
+ }, pagination)) // If no pagination this merged object will be empty
1101
+ } else if (isPaginated) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
1112
1102
  <span class="cstat-no" title="statement not covered" > // Copy pagination information if any so that client can use it anyway</span>
1113
1103
  <span class="cstat-no" title="statement not covered" > _.set(hook, options.dataPath || 'result', Object.assign({</span>
1114
1104
  <span class="cstat-no" title="statement not covered" > data: results</span>
1115
1105
  <span class="cstat-no" title="statement not covered" > }, pagination))</span>
1116
- <span class="cstat-no" title="statement not covered" > } else {</span>
1117
- <span class="cstat-no" title="statement not covered" > _.set(hook, options.dataPath || 'result', isFeatureCollection ? results : results[0])</span>
1118
- <span class="cstat-no" title="statement not covered" > }</span>
1119
- <span class="cstat-no" title="statement not covered" > }</span>
1120
- <span class="cstat-no" title="statement not covered" >}</span>
1121
- <span class="cstat-no" title="statement not covered" ></span>
1122
- <span class="cstat-no" title="statement not covered" >export async function aggregateFeaturesQuery (hook) {</span>
1123
- <span class="cstat-no" title="statement not covered" > const query = hook.params.query</span>
1124
- <span class="cstat-no" title="statement not covered" > const service = hook.service</span>
1125
- <span class="cstat-no" title="statement not covered" > if (!query) return</span>
1126
- <span class="cstat-no" title="statement not covered" > // Perform aggregation</span>
1127
- <span class="cstat-no" title="statement not covered" > if (query.$aggregate) {</span>
1128
- <span class="cstat-no" title="statement not covered" > const collection = service.Model</span>
1129
- <span class="cstat-no" title="statement not covered" > const indexes = await collection.indexes()</span>
1130
- <span class="cstat-no" title="statement not covered" > let featureId = (service.options ? service.options.featureId : [])</span>
1131
- <span class="cstat-no" title="statement not covered" > // Support compound ID</span>
1132
- <span class="cstat-no" title="statement not covered" > featureId = (Array.isArray(featureId) ? featureId : [featureId])</span>
1133
- <span class="cstat-no" title="statement not covered" > let featureIdType = (service.options ? service.options.featureIdType : [])</span>
1134
- <span class="cstat-no" title="statement not covered" > featureIdType = (Array.isArray(featureIdType) ? featureIdType : [featureIdType])</span>
1135
- <span class="cstat-no" title="statement not covered" > const ids = typeof query.$groupBy === 'string' // Group by matching ID(s), ie single ID or array of field to create a compound ID</span>
1136
- <span class="cstat-no" title="statement not covered" > ? { [query.$groupBy]: '$properties.' + query.$groupBy }</span>
1137
- <span class="cstat-no" title="statement not covered" > // Aggregated in an accumulator to avoid conflict with feature properties</span>
1138
- <span class="cstat-no" title="statement not covered" > : query.$groupBy.reduce((object, id) =&gt; Object.assign(object, { [id]: '$properties.' + id }), {})</span>
1139
- <span class="cstat-no" title="statement not covered" > const groupBy = { _id: ids }</span>
1140
- <span class="cstat-no" title="statement not covered" > let keys = _.keys(ids)</span>
1141
- <span class="cstat-no" title="statement not covered" > // Do we only keep first or last available time ?</span>
1142
- <span class="cstat-no" title="statement not covered" > const singleTime = (_.toNumber(query.$limit) === 1)</span>
1143
- <span class="cstat-no" title="statement not covered" > if (singleTime) {</span>
1144
- <span class="cstat-no" title="statement not covered" > // When single time no aggregation is performed at all so we only have raw features</span>
1145
- <span class="cstat-no" title="statement not covered" > keys = keys.map(key =&gt; 'properties.' + key)</span>
1146
- <span class="cstat-no" title="statement not covered" > // In this case no need to aggregate on each element we simply keep the first feature</span>
1147
- <span class="cstat-no" title="statement not covered" > // BUG: according to https://jira.mongodb.org/browse/SERVER-9507 MongoDB is not yet</span>
1148
- <span class="cstat-no" title="statement not covered" > // able to optimize this kind of operations to avoid full index scan</span>
1149
- <span class="cstat-no" title="statement not covered" > // For now we should restrict it to short time range</span>
1150
- <span class="cstat-no" title="statement not covered" > Object.assign(groupBy, { feature: { $first: '$$ROOT' } })</span>
1151
- <span class="cstat-no" title="statement not covered" > } else {</span>
1152
- <span class="cstat-no" title="statement not covered" > Object.assign(groupBy, {</span>
1153
- <span class="cstat-no" title="statement not covered" > time: { $push: '$time' }, // Keep track of all times</span>
1154
- <span class="cstat-no" title="statement not covered" > runTime: { $push: '$runTime' }, // Keep track of all run times</span>
1155
- <span class="cstat-no" title="statement not covered" > type: { $last: '$type' }, // type is assumed similar for all results, keep last</span>
1156
- <span class="cstat-no" title="statement not covered" > properties: { $last: '$properties' } // non-aggregated properties are assumed similar for all results, keep last</span>
1157
- <span class="cstat-no" title="statement not covered" > })</span>
1158
- <span class="cstat-no" title="statement not covered" > // Keep track of all levels as well if not targetting a specific one</span>
1159
- <span class="cstat-no" title="statement not covered" > if (!_.has(query, 'level')) {</span>
1160
- <span class="cstat-no" title="statement not covered" > Object.assign(groupBy, {</span>
1161
- <span class="cstat-no" title="statement not covered" > level: { $push: '$level' }</span>
1162
- <span class="cstat-no" title="statement not covered" > })</span>
1163
- <span class="cstat-no" title="statement not covered" > }</span>
1164
- <span class="cstat-no" title="statement not covered" > // Check if we aggregate geometry or simply properties</span>
1165
- <span class="cstat-no" title="statement not covered" > if (!query.$aggregate.includes('geometry')) {</span>
1166
- <span class="cstat-no" title="statement not covered" > Object.assign(groupBy, {</span>
1167
- <span class="cstat-no" title="statement not covered" > geometry: { $last: '$geometry' } // geometry is assumed similar for all results, keep last</span>
1168
- <span class="cstat-no" title="statement not covered" > })</span>
1169
- <span class="cstat-no" title="statement not covered" > }</span>
1170
- <span class="cstat-no" title="statement not covered" > }</span>
1171
- <span class="cstat-no" title="statement not covered" > // Merge with any additional group expression</span>
1172
- <span class="cstat-no" title="statement not covered" > const group = _.get(query, '$group', {})</span>
1173
- <span class="cstat-no" title="statement not covered" > Object.assign(groupBy, group)</span>
1174
- <span class="cstat-no" title="statement not covered" > // The query contains the match stage except options relevent to the aggregation pipeline</span>
1175
- <span class="cstat-no" title="statement not covered" > const match = _.omit(query, ['$group', '$groupBy', '$aggregate', '$geoNear', '$sort', '$limit', '$skip'])</span>
1176
- <span class="cstat-no" title="statement not covered" > // Check for any required type conversion (eg HTTP requests)</span>
1177
- <span class="cstat-no" title="statement not covered" > for (let i = 0; i &lt; featureId.length; i++) {</span>
1178
- <span class="cstat-no" title="statement not covered" > const id = featureId[i]</span>
1179
- <span class="cstat-no" title="statement not covered" > const idType = featureIdType[i]</span>
1180
- <span class="cstat-no" title="statement not covered" > if (_.has(match, 'properties.' + id)) {</span>
1181
- <span class="cstat-no" title="statement not covered" > if (idType === 'number') _.set(match, 'properties.' + id, _.toNumber(_.get(match, 'properties.' + id)))</span>
1182
- <span class="cstat-no" title="statement not covered" > }</span>
1183
- <span class="cstat-no" title="statement not covered" > }</span>
1184
- <span class="cstat-no" title="statement not covered" > // Ensure we do not mix results with/without relevant element values</span>
1185
- <span class="cstat-no" title="statement not covered" > // by separately querying each element then merging</span>
1186
- <span class="cstat-no" title="statement not covered" > let aggregatedResults</span>
1187
- <span class="cstat-no" title="statement not covered" > const aggregateOptions = {</span>
1188
- <span class="cstat-no" title="statement not covered" > allowDiskUse: true</span>
1189
- <span class="cstat-no" title="statement not covered" > }</span>
1190
- <span class="cstat-no" title="statement not covered" > await Promise.all(query.$aggregate.map(async element =&gt; {</span>
1191
- <span class="cstat-no" title="statement not covered" > const isGeometry = (element === 'geometry')</span>
1192
- <span class="cstat-no" title="statement not covered" > // Geometry is a root property while others are feature properties</span>
1193
- <span class="cstat-no" title="statement not covered" > const prefix = (isGeometry ? '' : 'properties.')</span>
1194
- <span class="cstat-no" title="statement not covered" > const pipeline = []</span>
1195
- <span class="cstat-no" title="statement not covered" > // Check for geometry stage</span>
1196
- <span class="cstat-no" title="statement not covered" > if (query.$geoNear) {</span>
1106
+ } else {
1107
+ _.set(hook, options.dataPath || 'result', isFeatureCollection ? results <span class="branch-0 cbranch-no" title="branch not covered" >: results[0])</span>
1108
+ }
1109
+ }
1110
+ }
1111
+ &nbsp;
1112
+ export async function aggregateFeaturesQuery (hook) {
1113
+ const query = hook.params.query
1114
+ const service = hook.service
1115
+ if (!query) <span class="branch-0 cbranch-no" title="branch not covered" >return</span>
1116
+ // Perform aggregation
1117
+ if (query.$aggregate) {
1118
+ const collection = service.Model
1119
+ const indexes = await collection.indexes()
1120
+ let featureId = (service.options ? service.options.featureId <span class="branch-0 cbranch-no" title="branch not covered" >: [])</span>
1121
+ // Support compound ID
1122
+ featureId = (Array.isArray(featureId) <span class="branch-0 cbranch-no" title="branch not covered" >? featureId </span>: [featureId])
1123
+ let featureIdType = (service.options ? service.options.featureIdType <span class="branch-0 cbranch-no" title="branch not covered" >: [])</span>
1124
+ featureIdType = (Array.isArray(featureIdType) <span class="branch-0 cbranch-no" title="branch not covered" >? featureIdType </span>: [featureIdType])
1125
+ const ids = typeof query.$groupBy === 'string' // Group by matching ID(s), ie single ID or array of field to create a compound ID
1126
+ ? { [query.$groupBy]: '$properties.' + query.$groupBy }
1127
+ // Aggregated in an accumulator to avoid conflict with feature properties
1128
+ <span class="branch-0 cbranch-no" title="branch not covered" > : query.$groupBy.reduce((object, id) =&gt; Object.assign(object, { [id]: '$properties.' + id }), {})</span>
1129
+ const groupBy = { _id: ids }
1130
+ let keys = _.keys(ids)
1131
+ // Do we only keep first or last available time ?
1132
+ const singleTime = (_.toNumber(query.$limit) === 1)
1133
+ if (singleTime) {
1134
+ // When single time no aggregation is performed at all so we only have raw features
1135
+ keys = keys.map(key =&gt; 'properties.' + key)
1136
+ // In this case no need to aggregate on each element we simply keep the first feature
1137
+ // BUG: according to https://jira.mongodb.org/browse/SERVER-9507 MongoDB is not yet
1138
+ // able to optimize this kind of operations to avoid full index scan
1139
+ // For now we should restrict it to short time range
1140
+ Object.assign(groupBy, { feature: { $first: '$$ROOT' } })
1141
+ } else {
1142
+ Object.assign(groupBy, {
1143
+ time: { $push: '$time' }, // Keep track of all times
1144
+ runTime: { $push: '$runTime' }, // Keep track of all run times
1145
+ type: { $last: '$type' }, // type is assumed similar for all results, keep last
1146
+ properties: { $last: '$properties' } // non-aggregated properties are assumed similar for all results, keep last
1147
+ })
1148
+ // Keep track of all levels as well if not targetting a specific one
1149
+ if (!_.has(query, 'level')) {
1150
+ Object.assign(groupBy, {
1151
+ level: { $push: '$level' }
1152
+ })
1153
+ }
1154
+ // Check if we aggregate geometry or simply properties
1155
+ if (!query.$aggregate.includes('geometry')) {
1156
+ Object.assign(groupBy, {
1157
+ geometry: { $last: '$geometry' } // geometry is assumed similar for all results, keep last
1158
+ })
1159
+ }
1160
+ }
1161
+ // Merge with any additional group expression
1162
+ const group = _.get(query, '$group', {})
1163
+ Object.assign(groupBy, group)
1164
+ // The query contains the match stage except options relevent to the aggregation pipeline
1165
+ const match = _.omit(query, ['$group', '$groupBy', '$aggregate', '$geoNear', '$sort', '$limit', '$skip'])
1166
+ // Check for any required type conversion (eg HTTP requests)
1167
+ for (let i = 0; i &lt; featureId.length; i++) {
1168
+ const id = featureId[i]
1169
+ const idType = featureIdType[i]
1170
+ if (_.has(match, 'properties.' + id)) {
1171
+ if (idType === 'number') <span class="branch-0 cbranch-no" title="branch not covered" >_.set(match, 'properties.' + id, _.toNumber(_.get(match, 'properties.' + id)))</span>
1172
+ }
1173
+ }
1174
+ // Ensure we do not mix results with/without relevant element values
1175
+ // by separately querying each element then merging
1176
+ let aggregatedResults
1177
+ // Associative map used to optimize merging between aggregated elements
1178
+ const aggregatedResultsMap = new Map()
1179
+ const aggregateOptions = {
1180
+ allowDiskUse: true
1181
+ }
1182
+ await Promise.all(query.$aggregate.map(async element =&gt; {
1183
+ const isGeometry = (element === 'geometry')
1184
+ // Geometry is a root property while others are feature properties
1185
+ const prefix = (isGeometry ? '' : 'properties.')
1186
+ const pipeline = []
1187
+ // Check for geometry stage
1188
+ if (query.$geoNear) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
1197
1189
  <span class="cstat-no" title="statement not covered" > pipeline.push({ $geoNear: query.$geoNear })</span>
1198
1190
  <span class="cstat-no" title="statement not covered" > }</span>
1199
- <span class="cstat-no" title="statement not covered" > // Find matching features only</span>
1200
- <span class="cstat-no" title="statement not covered" > pipeline.push({ $match: Object.assign({ [prefix + element]: { $exists: true } }, match) })</span>
1201
- <span class="cstat-no" title="statement not covered" > // Ensure they are ordered by increasing time by default,</span>
1202
- <span class="cstat-no" title="statement not covered" > // most recent forecast and lower level first</span>
1203
- <span class="cstat-no" title="statement not covered" > pipeline.push({ $sort: Object.assign({ time: 1, runTime: -1, level: 1 }, query.$sort) })</span>
1204
- <span class="cstat-no" title="statement not covered" > // Keep track of all feature values</span>
1205
- <span class="cstat-no" title="statement not covered" > if (singleTime) {</span>
1206
- <span class="cstat-no" title="statement not covered" > pipeline.push({ $group: groupBy })</span>
1207
- <span class="cstat-no" title="statement not covered" > // As we replace the root document with the feature in this case keep track of any accumlated element before</span>
1208
- <span class="cstat-no" title="statement not covered" > // If the accumulated properties is name maxProperty then we copy it in the feature as feature.properties.maxProperty</span>
1209
- <span class="cstat-no" title="statement not covered" > if (!_.isEmpty(group)) {</span>
1210
- <span class="cstat-no" title="statement not covered" > pipeline.push({</span>
1211
- <span class="cstat-no" title="statement not covered" > $set: _.mapKeys(_.mapValues(group, (value, key) =&gt; `$${key}`), (value, key) =&gt; `feature.properties.${key}`)</span>
1212
- <span class="cstat-no" title="statement not covered" > })</span>
1213
- <span class="cstat-no" title="statement not covered" > }</span>
1214
- <span class="cstat-no" title="statement not covered" > pipeline.push({ $replaceRoot: { newRoot: '$feature' } })</span>
1215
- <span class="cstat-no" title="statement not covered" > } else {</span>
1216
- <span class="cstat-no" title="statement not covered" > pipeline.push({ $group: Object.assign({ [element]: { $push: '$' + prefix + element } }, groupBy) })</span>
1217
- <span class="cstat-no" title="statement not covered" > }</span>
1218
- <span class="cstat-no" title="statement not covered" > debug(`Aggregating ${element} element for features`)</span>
1219
- <span class="cstat-no" title="statement not covered" > pipeline.forEach(stage =&gt; {</span>
1220
- <span class="cstat-no" title="statement not covered" > _.forOwn(stage, (value, key) =&gt; debug('Stage', key, value))</span>
1221
- <span class="cstat-no" title="statement not covered" > })</span>
1222
- <span class="cstat-no" title="statement not covered" > // Provide a hint to the aggregation targeting feature ID and aggregation elements.</span>
1223
- <span class="cstat-no" title="statement not covered" > // The problem with the aggregation hint option is that it should correspond</span>
1224
- <span class="cstat-no" title="statement not covered" > // exactly to an existing index otherwise it raises an error.</span>
1225
- <span class="cstat-no" title="statement not covered" > // We use a convention to get the order right: geometry =&gt; feature ID =&gt; aggregated element =&gt; time.</span>
1226
- <span class="cstat-no" title="statement not covered" > // We check anyway if the index does exist to avoid any error</span>
1227
- <span class="cstat-no" title="statement not covered" > // FIXME: Instead of assuming the appropriate index is defined in the right order,</span>
1228
- <span class="cstat-no" title="statement not covered" > // we might select the "best" available index (ie having the most similarities with the required one).</span>
1229
- <span class="cstat-no" title="statement not covered" > const hint = {}</span>
1230
- <span class="cstat-no" title="statement not covered" > if (isGeometry || query.$geoNear || match.geometry) Object.assign(hint, { geometry: '2dsphere' })</span>
1231
- <span class="cstat-no" title="statement not covered" > featureId.forEach(id =&gt; {</span>
1232
- <span class="cstat-no" title="statement not covered" > hint['properties.' + id] = 1</span>
1233
- <span class="cstat-no" title="statement not covered" > })</span>
1234
- <span class="cstat-no" title="statement not covered" > Object.assign(hint, { ['properties.' + element]: 1 })</span>
1235
- <span class="cstat-no" title="statement not covered" > // Use provided sort time option if any</span>
1236
- <span class="cstat-no" title="statement not covered" > hint.time = _.get(query, '$sort.time', 1)</span>
1237
- <span class="cstat-no" title="statement not covered" > const hintIndexName = _.reduce(hint, (name, value, key) =&gt; name ? `${name}_${key}_${value}` : `${key}_${value}`, '')</span>
1238
- <span class="cstat-no" title="statement not covered" > debug('Best aggregation hint index found', hintIndexName)</span>
1239
- <span class="cstat-no" title="statement not covered" > const hintIndex = _.find(indexes, { name: hintIndexName })</span>
1240
- <span class="cstat-no" title="statement not covered" > const aggregateElementOptions = Object.assign({}, aggregateOptions)</span>
1241
- <span class="cstat-no" title="statement not covered" > if (hintIndex) aggregateElementOptions.hint = hintIndexName</span>
1242
- <span class="cstat-no" title="statement not covered" > debug('Aggregation options', aggregateElementOptions)</span>
1243
- <span class="cstat-no" title="statement not covered" > const elementResults = await collection.aggregate(pipeline, aggregateElementOptions).toArray()</span>
1244
- <span class="cstat-no" title="statement not covered" > debug(`Generated ${elementResults.length} feature(s) for ${element} element`, elementResults)</span>
1245
- <span class="cstat-no" title="statement not covered" > // Rearrange data so that we get ordered arrays indexed by element</span>
1246
- <span class="cstat-no" title="statement not covered" > elementResults.forEach(result =&gt; {</span>
1247
- <span class="cstat-no" title="statement not covered" > result.time = { [element]: result.time }</span>
1248
- <span class="cstat-no" title="statement not covered" > if (result.runTime) result.runTime = { [element]: result.runTime }</span>
1249
- <span class="cstat-no" title="statement not covered" > if (!singleTime &amp;&amp; !isGeometry) {</span>
1250
- <span class="cstat-no" title="statement not covered" > // Set back the element values as properties because we aggregated in an accumulator</span>
1251
- <span class="cstat-no" title="statement not covered" > // to avoid conflict with non-aggregated feature properties</span>
1252
- <span class="cstat-no" title="statement not covered" > _.set(result, prefix + element, _.get(result, element))</span>
1253
- <span class="cstat-no" title="statement not covered" > // Delete accumulator</span>
1254
- <span class="cstat-no" title="statement not covered" > _.unset(result, element)</span>
1255
- <span class="cstat-no" title="statement not covered" > }</span>
1256
- <span class="cstat-no" title="statement not covered" > })</span>
1257
- <span class="cstat-no" title="statement not covered" > // Now merge with previous element results</span>
1258
- <span class="cstat-no" title="statement not covered" > if (!aggregatedResults) {</span>
1259
- <span class="cstat-no" title="statement not covered" > aggregatedResults = elementResults</span>
1260
- <span class="cstat-no" title="statement not covered" > } else {</span>
1261
- <span class="cstat-no" title="statement not covered" > elementResults.forEach(result =&gt; {</span>
1262
- <span class="cstat-no" title="statement not covered" > const resultKeys = _.pick(result._id, keys)</span>
1263
- <span class="cstat-no" title="statement not covered" > const previousResult = aggregatedResults.find(aggregatedResult =&gt; {</span>
1264
- <span class="cstat-no" title="statement not covered" > const aggregatedResultKeys = _.pick(aggregatedResult._id, keys)</span>
1265
- <span class="cstat-no" title="statement not covered" > return _.isEqual(aggregatedResultKeys, resultKeys)</span>
1266
- <span class="cstat-no" title="statement not covered" > })</span>
1267
- <span class="cstat-no" title="statement not covered" > // Merge with previous matching feature if any</span>
1268
- <span class="cstat-no" title="statement not covered" > if (previousResult) {</span>
1269
- <span class="cstat-no" title="statement not covered" > Object.assign(previousResult.time, result.time)</span>
1270
- <span class="cstat-no" title="statement not covered" > if (result.runTime) {</span>
1271
- <span class="cstat-no" title="statement not covered" > if (previousResult.runTime) Object.assign(previousResult.runTime, result.runTime)</span>
1272
- <span class="cstat-no" title="statement not covered" > else previousResult.runTime = { [element]: result.runTime }</span>
1273
- <span class="cstat-no" title="statement not covered" > }</span>
1274
- <span class="cstat-no" title="statement not covered" > _.set(previousResult, prefix + element, _.get(result, prefix + element))</span>
1275
- <span class="cstat-no" title="statement not covered" > } else {</span>
1191
+ // Find matching features only
1192
+ pipeline.push({ $match: Object.assign({ [prefix + element]: { $exists: true } }, match) })
1193
+ // Ensure they are ordered by increasing time by default,
1194
+ // most recent forecast and lower level first
1195
+ pipeline.push({ $sort: Object.assign({ time: 1, runTime: -1, level: 1 }, query.$sort) })
1196
+ // Keep track of all feature values
1197
+ if (singleTime) {
1198
+ pipeline.push({ $group: groupBy })
1199
+ // As we replace the root document with the feature in this case keep track of any accumlated element before
1200
+ // If the accumulated properties is name maxProperty then we copy it in the feature as feature.properties.maxProperty
1201
+ if (!_.isEmpty(group)) {
1202
+ pipeline.push({
1203
+ $set: _.mapKeys(_.mapValues(group, (value, key) =&gt; `$${key}`), (value, key) =&gt; `feature.properties.${key}`)
1204
+ })
1205
+ }
1206
+ pipeline.push({ $replaceRoot: { newRoot: '$feature' } })
1207
+ } else {
1208
+ pipeline.push({ $group: Object.assign({ [element]: { $push: '$' + prefix + element } }, groupBy) })
1209
+ }
1210
+ debug(`Aggregating ${element} element for features`)
1211
+ pipeline.forEach(stage =&gt; {
1212
+ _.forOwn(stage, (value, key) =&gt; debug('Stage', key, value))
1213
+ })
1214
+ const aggregateElementOptions = Object.assign({}, aggregateOptions)
1215
+ debug('Aggregation options', aggregateElementOptions)
1216
+ const elementResults = await collection.aggregate(pipeline, aggregateElementOptions).toArray()
1217
+ debug(`Generated ${elementResults.length} feature(s) for ${element} element, picked first two`, elementResults.slice(0,2))
1218
+ // Rearrange data so that we get ordered arrays indexed by element
1219
+ elementResults.forEach(result =&gt; {
1220
+ result.time = { [element]: result.time }
1221
+ if (result.runTime) result.runTime = { [element]: result.runTime }
1222
+ if (!singleTime &amp;&amp; !isGeometry) {
1223
+ // Set back the element values as properties because we aggregated in an accumulator
1224
+ // to avoid conflict with non-aggregated feature properties
1225
+ _.set(result, prefix + element, _.get(result, element))
1226
+ // Delete accumulator
1227
+ _.unset(result, element)
1228
+ }
1229
+ })
1230
+ // Now merge with previous element results
1231
+ if (!aggregatedResults) {
1232
+ aggregatedResults = elementResults
1233
+ aggregatedResults.forEach(result =&gt; {
1234
+ // Keep track of result in map to improve search later
1235
+ const resultKeys = keys.map(key =&gt; _.get(singleTime ? result : result._id, key))
1236
+ const resultKey = resultKeys.join('-')
1237
+ aggregatedResultsMap.set(resultKey, result)
1238
+ })
1239
+ } else {
1240
+ elementResults.forEach(result =&gt; {
1241
+ // When single time no aggregation is performed at all so we only have raw features
1242
+ const resultKeys = keys.map(key =&gt; _.get(singleTime ? result : result._id, key))
1243
+ /* Optimize previous result search with map, kept the naive code here for reference/debug purpose
1244
+ const previousResult = aggregatedResults.find(aggregatedResult =&gt; {
1245
+ const aggregatedResultKeys = keys.map(key =&gt; _.get(singleTime ? result : result._id, key))
1246
+ return _.isEqual(aggregatedResultKeys, resultKeys)
1247
+ })
1248
+ */
1249
+ const resultKey = resultKeys.join('-')
1250
+ const previousResult = aggregatedResultsMap.get(resultKey)
1251
+ // Merge with previous matching feature if any
1252
+ if (previousResult) {
1253
+ Object.assign(previousResult.time, result.time)
1254
+ if (result.runTime) {
1255
+ if (previousResult.runTime) Object.assign(previousResult.runTime, result.runTime)
1256
+ <span class="cstat-no" title="statement not covered" ><span class="branch-0 cbranch-no" title="branch not covered" > else previousResult.runTime = { [element]: result.runTime }</span></span>
1257
+ }
1258
+ _.set(previousResult, prefix + element, _.get(result, prefix + element))
1259
+ }<span class="branch-0 cbranch-no" title="branch not covered" > else {</span>
1276
1260
  <span class="cstat-no" title="statement not covered" > aggregatedResults.push(result)</span>
1261
+ <span class="cstat-no" title="statement not covered" > aggregatedResultsMap.set(resultKey, result)</span>
1277
1262
  <span class="cstat-no" title="statement not covered" > }</span>
1278
- <span class="cstat-no" title="statement not covered" > })</span>
1279
- <span class="cstat-no" title="statement not covered" > }</span>
1280
- <span class="cstat-no" title="statement not covered" > }))</span>
1281
- <span class="cstat-no" title="statement not covered" > delete query.$groupBy</span>
1282
- <span class="cstat-no" title="statement not covered" > delete query.$group</span>
1283
- <span class="cstat-no" title="statement not covered" > delete query.$aggregate</span>
1284
- <span class="cstat-no" title="statement not covered" > delete query.$geoNear</span>
1285
- <span class="cstat-no" title="statement not covered" > // Set result to avoid service DB call</span>
1286
- <span class="cstat-no" title="statement not covered" > hook.result = aggregatedResults</span>
1287
- <span class="cstat-no" title="statement not covered" > }</span>
1288
- <span class="cstat-no" title="statement not covered" > return hook</span>
1289
- <span class="cstat-no" title="statement not covered" >}</span>
1263
+ })
1264
+ }
1265
+ }))
1266
+ delete query.$groupBy
1267
+ delete query.$group
1268
+ delete query.$aggregate
1269
+ delete query.$geoNear
1270
+ // Set result to avoid service DB call
1271
+ hook.result = aggregatedResults <span class="branch-0 cbranch-no" title="branch not covered" >|| []</span>
1272
+ }
1273
+ return hook
1274
+ }
1290
1275
  &nbsp;</pre></td></tr></table></pre>
1291
1276
 
1292
1277
  <div class='push'></div><!-- for sticky footer -->
@@ -1294,7 +1279,7 @@
1294
1279
  <div class='footer quiet pad2 space-top1 center small'>
1295
1280
  Code coverage generated by
1296
1281
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
1297
- at 2025-02-18T09:52:45.637Z
1282
+ at 2025-07-24T10:00:20.508Z
1298
1283
  </div>
1299
1284
  <script src="../../../prettify.js"></script>
1300
1285
  <script>