@kalisio/kdk 2.1.9 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.travis.test.sh +42 -10
- package/README.md +2 -2
- package/core/api/application.js +7 -2
- package/core/api/authentication.js +17 -1
- package/core/api/db.js +7 -2
- package/core/api/hooks/hooks.authentication.js +4 -2
- package/core/api/hooks/hooks.authorisations.js +12 -2
- package/core/api/hooks/hooks.model.js +5 -5
- package/core/api/hooks/hooks.organisations.js +0 -4
- package/core/api/services/account/account.hooks.js +10 -6
- package/core/api/services/account/account.service.js +1 -1
- package/{map/api/services/geocoder/geocoder.hooks.js → core/api/services/import-export/import-export.hooks.js} +7 -5
- package/core/api/services/import-export/import-export.service.js +11 -0
- package/core/api/services/index.js +13 -1
- package/core/api/services/users/users.hooks.js +2 -3
- package/core/client/api.js +16 -14
- package/core/client/capabilities.js +6 -2
- package/core/client/components/KContent.vue +11 -1
- package/core/client/components/KDialog.vue +17 -15
- package/core/client/components/KModal.vue +1 -1
- package/core/client/components/KSponsor.vue +1 -1
- package/core/client/components/KTextArea.vue +5 -1
- package/core/client/components/app/KAbout.vue +1 -2
- package/core/client/components/app/KWelcome.vue +3 -5
- package/core/client/components/chart/KTimeSeriesChart.vue +24 -37
- package/core/client/components/collection/KColumn.vue +20 -17
- package/core/client/components/editor/KModalEditor.vue +0 -2
- package/core/client/components/form/KChipsField.vue +12 -2
- package/core/client/components/form/KColorField.vue +12 -2
- package/core/client/components/form/KColorScaleField.vue +12 -2
- package/core/client/components/form/KDateTimeRangeField.vue +12 -2
- package/core/client/components/form/KDatetimeField.vue +12 -2
- package/core/client/components/form/KEmailField.vue +12 -2
- package/core/client/components/form/KFileField.vue +12 -2
- package/core/client/components/form/KForm.vue +47 -9
- package/core/client/components/form/KIconField.vue +12 -2
- package/core/client/components/form/KItemField.vue +25 -4
- package/core/client/components/form/KNumberField.vue +12 -2
- package/core/client/components/form/KOptionsField.vue +12 -2
- package/core/client/components/form/KPasswordField.vue +12 -2
- package/core/client/components/form/KPhoneField.vue +13 -3
- package/core/client/components/form/KPropertyItemField.vue +12 -2
- package/core/client/components/form/KResolutionField.vue +126 -0
- package/core/client/components/form/KRoleField.vue +12 -2
- package/core/client/components/form/KSelectField.vue +14 -4
- package/core/client/components/form/KTextField.vue +12 -2
- package/core/client/components/form/KTextareaField.vue +13 -3
- package/core/client/components/form/KToggleField.vue +12 -2
- package/core/client/components/form/KTokenField.vue +12 -2
- package/core/client/components/form/KUnitField.vue +12 -2
- package/core/client/components/form/KUrlField.vue +12 -2
- package/core/client/components/input/KIconChooser.vue +10 -12
- package/core/client/components/input/KPalette.vue +2 -1
- package/core/client/components/layout/KPage.vue +5 -4
- package/core/client/components/layout/KWindow.vue +10 -10
- package/core/client/components/media/KColorScale.vue +25 -19
- package/core/client/components/media/KImageViewer.vue +57 -33
- package/core/client/components/media/KShape.vue +14 -103
- package/core/client/components/screen/KRegisterScreen.vue +0 -1
- package/core/client/components/screen/KScreenFooter.vue +0 -18
- package/core/client/components/team/KAddMember.vue +16 -22
- package/core/client/components/team/KGroupsActivity.vue +14 -0
- package/core/client/components/team/KMembersActivity.vue +12 -0
- package/core/client/components/team/KTagsActivity.vue +14 -0
- package/core/client/components/time/KDateTime.vue +23 -7
- package/core/client/components/time/KTimeControl.vue +142 -0
- package/core/client/components/tool/KExportTool.vue +57 -0
- package/core/client/composables/collection.js +0 -1
- package/core/client/composables/pwa.js +0 -1
- package/core/client/composables/schema.js +1 -1
- package/core/client/composables/session.js +30 -6
- package/core/client/exporter.js +147 -0
- package/core/client/i18n/core_en.json +91 -23
- package/core/client/i18n/core_fr.json +92 -23
- package/core/client/index.js +3 -0
- package/core/client/layout.js +34 -14
- package/core/client/local-storage.js +8 -6
- package/core/client/mixins/index.js +0 -1
- package/core/client/mixins/mixin.base-field.js +24 -2
- package/core/client/mixins/mixin.object-proxy.js +0 -1
- package/core/client/search.js +2 -1
- package/core/client/services/index.js +2 -1
- package/core/client/services/local-settings.service.js +4 -4
- package/core/client/theme.js +3 -3
- package/core/client/time.js +4 -0
- package/core/client/units.js +150 -5
- package/core/client/utils/index.js +13 -6
- package/core/client/utils/utils.account.js +1 -1
- package/core/client/utils/utils.colors.js +43 -0
- package/core/client/utils/utils.platform.js +0 -1
- package/core/client/utils/utils.pwa.js +14 -14
- package/core/client/utils/utils.session.js +1 -1
- package/core/client/utils/utils.shapes.js +270 -0
- package/core/client/utils/utils.time.js +37 -0
- package/core/common/permissions.js +3 -0
- package/core/common/schemas/settings.update.json +50 -29
- package/extras/css/core.variables.scss +3 -1
- package/extras/icons/wind-speed-0.svg +8 -0
- package/extras/icons/wind-speed-10.svg +8 -0
- package/extras/icons/wind-speed-100.svg +12 -0
- package/extras/icons/wind-speed-105.svg +13 -0
- package/extras/icons/wind-speed-15.svg +9 -0
- package/extras/icons/wind-speed-20.svg +9 -0
- package/extras/icons/wind-speed-25.svg +10 -0
- package/extras/icons/wind-speed-30.svg +10 -0
- package/extras/icons/wind-speed-35.svg +11 -0
- package/extras/icons/wind-speed-40.svg +11 -0
- package/extras/icons/wind-speed-45.svg +12 -0
- package/extras/icons/wind-speed-5.svg +9 -0
- package/extras/icons/wind-speed-50.svg +9 -0
- package/extras/icons/wind-speed-55.svg +10 -0
- package/extras/icons/wind-speed-60.svg +10 -0
- package/extras/icons/wind-speed-65.svg +11 -0
- package/extras/icons/wind-speed-70.svg +11 -0
- package/extras/icons/wind-speed-75.svg +12 -0
- package/extras/icons/wind-speed-80.svg +12 -0
- package/extras/icons/wind-speed-85.svg +13 -0
- package/extras/icons/wind-speed-90.svg +13 -0
- package/extras/icons/wind-speed-95.svg +14 -0
- package/extras/tours/map/navigation-bar.js +17 -15
- package/extras/tours/map/timeline.js +33 -33
- package/map/api/config/categories.cjs +4 -1
- package/map/api/hooks/hooks.catalog.js +39 -0
- package/map/api/hooks/hooks.features.js +23 -3
- package/map/api/hooks/hooks.query.js +65 -21
- package/map/api/models/projects.model.mongodb.js +8 -0
- package/map/api/services/catalog/catalog.hooks.js +5 -3
- package/map/api/services/features/features.hooks.js +18 -6
- package/map/api/services/index.js +22 -6
- package/map/api/services/projects/projects.hooks.js +118 -0
- package/map/client/capture.js +16 -0
- package/map/client/cesium/utils/index.js +4 -0
- package/map/client/cesium/utils/utils.events.js +30 -0
- package/map/client/cesium/utils/utils.features.js +8 -0
- package/map/client/cesium/utils/utils.popup.js +17 -0
- package/map/client/cesium/utils/utils.style.js +137 -0
- package/map/client/components/KCapture.vue +50 -0
- package/map/client/components/KCaptureTextArea.vue +53 -0
- package/map/client/components/KCompass.vue +2 -2
- package/map/client/components/KFeaturesChart.vue +1 -1
- package/map/client/components/KFeaturesFilter.vue +2 -2
- package/map/client/components/KLayerStyleForm.vue +288 -454
- package/map/client/components/KLevelSlider.vue +1 -1
- package/map/client/components/KNorth.vue +31 -0
- package/map/client/components/KProjectMenu.vue +88 -0
- package/map/client/components/KTimezoneMap.vue +36 -24
- package/map/client/components/catalog/KAddLayer.vue +3 -4
- package/map/client/components/catalog/KConnectLayer.vue +20 -4
- package/map/client/components/catalog/KCreateLayer.vue +1 -2
- package/map/client/components/catalog/KCreateProject.vue +100 -0
- package/map/client/components/catalog/KCreateView.vue +25 -2
- package/map/client/components/catalog/KLayersPanel.vue +24 -27
- package/map/client/components/catalog/KLayersSelector.vue +1 -1
- package/map/client/components/catalog/KProjectEditor.vue +91 -0
- package/map/client/components/catalog/KProjectManager.vue +60 -0
- package/map/client/components/catalog/KProjectSelector.vue +38 -0
- package/map/client/components/catalog/KProjectsPanel.vue +153 -0
- package/map/client/components/catalog/KSelectLayers.vue +96 -0
- package/map/client/components/catalog/KSelectViews.vue +96 -0
- package/map/client/components/catalog/KViewsPanel.vue +66 -30
- package/map/client/components/form/KDirectionField.vue +24 -5
- package/map/client/components/form/KLayerCategoryField.vue +12 -2
- package/map/client/components/form/KLocationField.vue +20 -5
- package/map/client/components/form/KOwsLayerField.vue +12 -2
- package/map/client/components/form/KOwsServiceField.vue +12 -2
- package/map/client/components/form/KSelectLayersField.vue +159 -0
- package/map/client/components/form/KSelectViewsField.vue +121 -0
- package/map/client/components/form/KTimezoneField.vue +24 -17
- package/map/client/components/legend/KColorScaleLegend.vue +6 -2
- package/map/client/components/legend/KLayerLegend.vue +71 -0
- package/map/client/components/legend/KLegend.vue +54 -51
- package/map/client/components/legend/KLegendRenderer.vue +5 -3
- package/map/client/components/legend/KSymbolsLegend.vue +12 -10
- package/map/client/components/legend/KVariablesLegend.vue +78 -0
- package/map/client/components/location/KGeocodersFilter.vue +2 -4
- package/map/client/components/location/KLocationCardSection.vue +8 -4
- package/map/client/components/location/KLocationMap.vue +48 -17
- package/map/client/components/location/KLocationSearch.vue +13 -3
- package/map/client/components/tools/KSearchTool.vue +17 -12
- package/map/client/components/widget/KElevationProfile.vue +16 -19
- package/map/client/components/widget/KMapillaryViewer.vue +21 -22
- package/map/client/components/widget/KTimeSeries.vue +35 -29
- package/map/client/composables/activity.js +15 -2
- package/map/client/composables/catalog.js +81 -0
- package/map/client/composables/highlight.js +45 -30
- package/map/client/composables/index.js +2 -0
- package/map/client/composables/location.js +25 -18
- package/map/client/composables/probe.js +4 -1
- package/map/client/composables/project.js +122 -0
- package/map/client/composables/weather.js +3 -3
- package/map/client/geolocation.js +1 -1
- package/map/client/globe.js +2 -0
- package/map/client/i18n/map_en.json +127 -76
- package/map/client/i18n/map_fr.json +128 -72
- package/map/client/index.js +3 -0
- package/map/client/init.js +17 -0
- package/map/client/leaflet/GSMaPLayer.js +16 -17
- package/map/client/leaflet/ShapeMarker.js +51 -0
- package/map/client/leaflet/TiledFeatureLayer.js +39 -9
- package/map/client/leaflet/TiledMeshLayer.js +13 -15
- package/map/client/leaflet/TiledWindLayer.js +6 -10
- package/map/client/leaflet/utils/index.js +4 -0
- package/map/client/leaflet/utils/utils.events.js +41 -0
- package/map/client/leaflet/utils/utils.popup.js +21 -0
- package/map/client/leaflet/utils/utils.style.js +195 -0
- package/map/client/leaflet/utils/utils.tiles.js +87 -0
- package/map/client/map.js +2 -0
- package/map/client/mixins/globe/mixin.base-globe.js +39 -18
- package/map/client/mixins/globe/mixin.geojson-layers.js +139 -69
- package/map/client/mixins/globe/mixin.popup.js +2 -1
- package/map/client/mixins/globe/mixin.style.js +6 -4
- package/map/client/mixins/globe/mixin.tooltip.js +8 -3
- package/map/client/mixins/map/mixin.base-map.js +53 -28
- package/map/client/mixins/map/mixin.edit-layers.js +15 -15
- package/map/client/mixins/map/mixin.forecast-layers.js +3 -1
- package/map/client/mixins/map/mixin.geojson-layers.js +60 -20
- package/map/client/mixins/map/mixin.georaster-layers.js +4 -11
- package/map/client/mixins/map/mixin.heatmap-layers.js +1 -1
- package/map/client/mixins/map/mixin.popup.js +2 -1
- package/map/client/mixins/map/mixin.style.js +4 -67
- package/map/client/mixins/map/mixin.tiled-mesh-layers.js +2 -1
- package/map/client/mixins/map/mixin.tiled-wind-layers.js +4 -2
- package/map/client/mixins/map/mixin.tooltip.js +2 -1
- package/map/client/mixins/mixin.activity.js +71 -192
- package/map/client/mixins/mixin.catalog-panel.js +6 -6
- package/map/client/mixins/mixin.context.js +12 -9
- package/map/client/mixins/mixin.feature-service.js +29 -300
- package/map/client/mixins/mixin.weacast.js +11 -17
- package/map/client/pixi-utils.js +1 -1
- package/map/client/planets.js +66 -0
- package/map/client/utils/index.js +6 -0
- package/map/client/utils/utils.capture.js +176 -0
- package/map/client/utils/utils.catalog.js +166 -0
- package/map/client/utils/utils.features.js +364 -0
- package/map/client/utils/utils.js +0 -151
- package/map/client/utils/utils.layers.js +175 -0
- package/map/client/utils/utils.location.js +91 -23
- package/map/client/utils/utils.project.js +8 -0
- package/map/client/utils/utils.schema.js +0 -1
- package/map/client/utils/utils.style.js +309 -0
- package/map/client/utils.all.js +2 -2
- package/map/client/utils.globe.js +1 -1
- package/map/client/utils.map.js +1 -1
- package/map/common/permissions.js +2 -0
- package/map/common/schemas/capture.create.json +132 -0
- package/map/common/schemas/projects.create.json +52 -0
- package/map/common/schemas/projects.update.json +52 -0
- package/map/common/wms-utils.js +8 -3
- package/package.json +6 -5
- package/test/api/core/account.test.js +20 -0
- package/test/api/core/config/default.cjs +16 -3
- package/test/api/core/import-export.test.js +86 -0
- package/test/api/core/test-log-2024-01-04.log +14 -0
- package/test/api/map/catalog.test.js +164 -0
- package/test/api/map/index.test.js +25 -61
- package/test/api/map/test-log-2024-01-04.log +2 -0
- package/test/api/map/test-log-2024-01-11.log +1 -0
- package/test/api/map/test-log-2024-01-25.log +19 -0
- package/test/client/core/layout.js +24 -5
- package/test/client/core/utils.js +7 -0
- package/test/client/map/catalog.js +78 -1
- package/test/client/map/time.js +2 -1
- package/core/client/components/screen/KEndpointScreen.vue +0 -80
- package/core/client/mixins/mixin.account.js +0 -61
- package/extras/icons/kdk.png +0 -0
- package/map/api/services/geocoder/geocoder.service.js +0 -79
- package/map/client/cesium/utils.js +0 -133
- package/map/client/components/KCaptureToolbar.vue +0 -155
- package/map/client/components/KColorLegend.vue +0 -349
- package/map/client/components/KTimeline.vue +0 -293
- package/map/client/components/KUrlLegend.vue +0 -122
- package/map/client/leaflet/utils.js +0 -246
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import logger from 'loglevel'
|
|
3
|
+
import moment from 'moment'
|
|
4
|
+
import { getType } from '@turf/invariant'
|
|
5
|
+
import explode from '@turf/explode'
|
|
6
|
+
import kinks from '@turf/kinks'
|
|
7
|
+
import clean from '@turf/clean-coords'
|
|
8
|
+
import rhumbBearing from '@turf/rhumb-bearing'
|
|
9
|
+
import rhumbDistance from '@turf/rhumb-distance'
|
|
10
|
+
import rotate from '@turf/transform-rotate'
|
|
11
|
+
import scale from '@turf/transform-scale'
|
|
12
|
+
import translate from '@turf/transform-translate'
|
|
13
|
+
import { api, Time } from '../../../core/client/index.js'
|
|
14
|
+
|
|
15
|
+
export function processFeatures (geoJson, processor) {
|
|
16
|
+
const features = (geoJson.type === 'FeatureCollection' ? geoJson.features : [geoJson])
|
|
17
|
+
if (typeof processor === 'function') {
|
|
18
|
+
features.forEach(feature => processor(feature))
|
|
19
|
+
} else if (typeof processor === 'string') {
|
|
20
|
+
const compiler = _.template(processor)
|
|
21
|
+
features.forEach(feature => compiler({ feature, properties: feature.properties }))
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function transformFeatures (geoJson, transform) {
|
|
26
|
+
const features = (geoJson.type === 'FeatureCollection' ? geoJson.features : [geoJson])
|
|
27
|
+
features.forEach(feature => {
|
|
28
|
+
const scaling = _.get(transform, 'scale')
|
|
29
|
+
const rotation = _.get(transform, 'rotate')
|
|
30
|
+
const translation = _.get(transform, 'translate')
|
|
31
|
+
if (scaling) {
|
|
32
|
+
scale(feature, scaling.factor,
|
|
33
|
+
Object.assign(_.omit(scaling, ['factor']), { mutate: true }))
|
|
34
|
+
}
|
|
35
|
+
if (rotation) {
|
|
36
|
+
rotate(feature, rotation.angle,
|
|
37
|
+
Object.assign(_.omit(rotation, ['angle']), { mutate: true }))
|
|
38
|
+
}
|
|
39
|
+
if (translation) {
|
|
40
|
+
// Could be expressed as direction/distance or target point
|
|
41
|
+
// Take care that turfjs uses a rhumb line
|
|
42
|
+
if (translation.point) {
|
|
43
|
+
translation.distance = rhumbDistance(translation.pivot || [0, 0], translation.point)
|
|
44
|
+
translation.direction = rhumbBearing(translation.pivot || [0, 0], translation.point)
|
|
45
|
+
delete translation.pivot
|
|
46
|
+
delete translation.point
|
|
47
|
+
}
|
|
48
|
+
translate(feature, translation.distance, translation.direction,
|
|
49
|
+
Object.assign(_.omit(translation, ['direction', 'distance']), { mutate: true }))
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function getBaseQueryForFeatures (options) {
|
|
55
|
+
// Any base query to process ?
|
|
56
|
+
const baseQuery = {}
|
|
57
|
+
if (options.baseQuery) {
|
|
58
|
+
if (typeof options.baseQuery === 'function') {
|
|
59
|
+
const result = await options.baseQuery()
|
|
60
|
+
// A null query indicate to skip update
|
|
61
|
+
if (!result) return
|
|
62
|
+
else Object.assign(baseQuery, result)
|
|
63
|
+
} else {
|
|
64
|
+
Object.assign(baseQuery, options.baseQuery)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return baseQuery
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function getFilterQueryForFeatures (options) {
|
|
71
|
+
// Any filter query to process ?
|
|
72
|
+
const filterQuery = {}
|
|
73
|
+
if (options.filterQuery) {
|
|
74
|
+
if (typeof options.filterQuery === 'function') {
|
|
75
|
+
const result = await options.filterQuery()
|
|
76
|
+
Object.assign(filterQuery, result)
|
|
77
|
+
} else {
|
|
78
|
+
Object.assign(filterQuery, options.filterQuery)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Any filters to process ?
|
|
82
|
+
const filters = _.get(options, 'filters', [])
|
|
83
|
+
if (!_.isEmpty(filters)) {
|
|
84
|
+
// To be flexible enough filters can provide a query for their active and inactive state
|
|
85
|
+
// Similarly, filters can be combined with a different operator for each state
|
|
86
|
+
const filterOperators = _.get(options, 'filterOperators', { active: '$or', inactive: '$and' })
|
|
87
|
+
// Aggregate filter queries according to filter states
|
|
88
|
+
const activeFilters = filters
|
|
89
|
+
.filter(filter => filter.isActive)
|
|
90
|
+
.map(filter => filter.active)
|
|
91
|
+
.filter(query => !_.isEmpty(query))
|
|
92
|
+
if (!_.isEmpty(activeFilters)) filterQuery[filterOperators.active] = activeFilters
|
|
93
|
+
const inactiveFilters = filters
|
|
94
|
+
.filter(filter => !filter.isActive)
|
|
95
|
+
.map(filter => filter.inactive)
|
|
96
|
+
.filter(query => !_.isEmpty(query))
|
|
97
|
+
if (!_.isEmpty(inactiveFilters)) filterQuery[filterOperators.inactive] = inactiveFilters
|
|
98
|
+
}
|
|
99
|
+
return filterQuery
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function getSortQueryForFeatures (options) {
|
|
103
|
+
// Any sort query to process ?
|
|
104
|
+
const sortQuery = {}
|
|
105
|
+
if (options.sortQuery) {
|
|
106
|
+
if (typeof options.sortQuery === 'function') {
|
|
107
|
+
const result = await options.sortQuery()
|
|
108
|
+
Object.assign(sortQuery, result)
|
|
109
|
+
} else {
|
|
110
|
+
Object.assign(sortQuery, options.sortQuery)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return { $sort: sortQuery }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function getFeaturesUpdateInterval (options) {
|
|
117
|
+
const interval = _.get(options, 'every')
|
|
118
|
+
return (interval ? moment.duration(interval) : null)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function getFeaturesQueryInterval (options) {
|
|
122
|
+
const interval = getFeaturesUpdateInterval(options)
|
|
123
|
+
let queryInterval = _.get(options, 'queryFrom')
|
|
124
|
+
// If query interval not given use 2 x refresh interval as default value
|
|
125
|
+
// this ensures we cover last interval if server/client update processes are not in sync
|
|
126
|
+
if (!queryInterval && interval) queryInterval = moment.duration(-2 * interval.asMilliseconds())
|
|
127
|
+
return (queryInterval ? moment.duration(queryInterval) : null)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function shouldSkipFeaturesUpdate (lastUpdateTime, options, interval) {
|
|
131
|
+
// If not given try to compute query interval from options
|
|
132
|
+
if (!interval) {
|
|
133
|
+
interval = getFeaturesUpdateInterval(options)
|
|
134
|
+
}
|
|
135
|
+
// We assume this is not a time-varying layer
|
|
136
|
+
if (!interval) return true
|
|
137
|
+
const now = Time.getCurrentTime()
|
|
138
|
+
const elapsed = moment.duration(now.diff(lastUpdateTime))
|
|
139
|
+
// If query interval has elapsed since last update we need to update again
|
|
140
|
+
return (Math.abs(elapsed.asMilliseconds()) < interval.asMilliseconds())
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function getProbeFeatures (options) {
|
|
144
|
+
// Any base/filter/sort query to process ?
|
|
145
|
+
const query = await getBaseQueryForFeatures(options)
|
|
146
|
+
const filterQuery = await getFilterQueryForFeatures(options)
|
|
147
|
+
const sortQuery = await getSortQueryForFeatures(options)
|
|
148
|
+
Object.assign(query, filterQuery, sortQuery)
|
|
149
|
+
// Check API to be used in case the layer is coming from a remote "planet"
|
|
150
|
+
const planetApi = (typeof options.getPlanetApi === 'function' ? options.getPlanetApi() : api)
|
|
151
|
+
const response = await planetApi.getService(options.probeService).find(Object.assign({ query }, options.baseParams))
|
|
152
|
+
if (options.processor) processFeatures(response, options.processor)
|
|
153
|
+
if (options.transform) transformFeatures(response, options.transform)
|
|
154
|
+
return response
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function getFeaturesQuery (options, queryInterval, queryLevel) {
|
|
158
|
+
// If not given try to compute query interval from options
|
|
159
|
+
if (!queryInterval) {
|
|
160
|
+
queryInterval = getFeaturesQueryInterval(options)
|
|
161
|
+
}
|
|
162
|
+
// Any base query to process ?
|
|
163
|
+
let query = await getBaseQueryForFeatures(options)
|
|
164
|
+
// Request features with at least one data available during last query interval
|
|
165
|
+
if (queryInterval) {
|
|
166
|
+
// Check if we have variables to be aggregated in time or not
|
|
167
|
+
if (options.variables) {
|
|
168
|
+
query = Object.assign({
|
|
169
|
+
$groupBy: options.featureId,
|
|
170
|
+
// Take care we might have multiple variables targetting the same value name
|
|
171
|
+
// but that differentiate using others properties (compound feature ID)
|
|
172
|
+
$aggregate: _.uniq(options.variables.map(variable => variable.name))
|
|
173
|
+
}, query)
|
|
174
|
+
} else if (options.featureId) {
|
|
175
|
+
query = Object.assign({
|
|
176
|
+
$groupBy: options.featureId,
|
|
177
|
+
$aggregate: ['geometry']
|
|
178
|
+
}, query)
|
|
179
|
+
}
|
|
180
|
+
const now = Time.getCurrentTime()
|
|
181
|
+
if (moment.isDuration(queryInterval)) {
|
|
182
|
+
// Depending on the duration format we might have negative or positive values
|
|
183
|
+
const gte = (queryInterval.asMilliseconds() > 0
|
|
184
|
+
? now.clone().subtract(queryInterval)
|
|
185
|
+
: now.clone().add(queryInterval))
|
|
186
|
+
const lte = now
|
|
187
|
+
Object.assign(query, {
|
|
188
|
+
$sort: { time: -1, runTime: -1 },
|
|
189
|
+
time: {
|
|
190
|
+
$gte: gte.toISOString(),
|
|
191
|
+
$lte: lte.toISOString()
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
// If we can aggregate then keep track of last element of each aggregation
|
|
195
|
+
if (options.featureId) query.$limit = 1
|
|
196
|
+
} else if (typeof queryInterval === 'object') {
|
|
197
|
+
query.time = queryInterval
|
|
198
|
+
} else {
|
|
199
|
+
Object.assign(query, {
|
|
200
|
+
$sort: { time: -1, runTime: -1 },
|
|
201
|
+
time: { $lte: now.toISOString() }
|
|
202
|
+
})
|
|
203
|
+
// If we can aggregate then keep track of last element of each aggregation
|
|
204
|
+
if (options.featureId) query.$limit = 1
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (!_.isNil(queryLevel)) {
|
|
208
|
+
query.level = queryLevel
|
|
209
|
+
}
|
|
210
|
+
// Any filter/sort query to process ?
|
|
211
|
+
const filterQuery = await getFilterQueryForFeatures(options)
|
|
212
|
+
const sortQuery = await getSortQueryForFeatures(options)
|
|
213
|
+
// Take care to not erase possible existing sort options
|
|
214
|
+
_.merge(query, filterQuery, sortQuery)
|
|
215
|
+
|
|
216
|
+
return query
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export async function getFeaturesFromQuery (options, query) {
|
|
220
|
+
// Check API to be used in case the layer is coming from a remote "planet"
|
|
221
|
+
const planetApi = (typeof options.getPlanetApi === 'function' ? options.getPlanetApi() : api)
|
|
222
|
+
const response = await planetApi.getService(options.service).find(Object.assign({ query }, options.baseParams))
|
|
223
|
+
if (options.processor) processFeatures(response, options.processor)
|
|
224
|
+
if (options.transform) transformFeatures(response, options.transform)
|
|
225
|
+
return response
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function checkFeatures (geoJson, options = {
|
|
229
|
+
kinks: true,
|
|
230
|
+
redundantCoordinates: true
|
|
231
|
+
}) {
|
|
232
|
+
const features = (geoJson.type === 'FeatureCollection' ? geoJson.features : [geoJson])
|
|
233
|
+
// Removes redundant coordinates
|
|
234
|
+
if (options.redundantCoordinates) {
|
|
235
|
+
features.forEach(feature => clean(feature, { mutate: true }))
|
|
236
|
+
}
|
|
237
|
+
// Filter invalid features
|
|
238
|
+
let kinksFeatures
|
|
239
|
+
if (options.kinks) {
|
|
240
|
+
kinksFeatures = _.remove(features, feature => {
|
|
241
|
+
const type = getType(feature)
|
|
242
|
+
if ((type === 'MultiPolygon') || (type === 'Polygon')) {
|
|
243
|
+
const invalidFeatures = kinks(feature)
|
|
244
|
+
return _.get(invalidFeatures, 'features', []).length > 0
|
|
245
|
+
} else { // No possible self-intersection on points, self-intersections allowed on lines
|
|
246
|
+
return false
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
return { kinks: kinksFeatures }
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export async function createFeatures (geoJson, layerId, chunkSize = 5000, processCallback) {
|
|
254
|
+
if (!layerId) return
|
|
255
|
+
const features = (geoJson.type === 'FeatureCollection' ? geoJson.features : [geoJson])
|
|
256
|
+
features.forEach(feature => {
|
|
257
|
+
// Remove any temporary ID as we will use the one from MongoDB
|
|
258
|
+
delete feature._id
|
|
259
|
+
feature.layer = layerId
|
|
260
|
+
})
|
|
261
|
+
// Single edition mode
|
|
262
|
+
if (features.length === 1) {
|
|
263
|
+
const feature = await api.getService('features').create(features[0])
|
|
264
|
+
return feature
|
|
265
|
+
} else {
|
|
266
|
+
// Create chunks to avoid reaching some limits (upload size, timeout, etc.)
|
|
267
|
+
// We create chunks according to the number of points and not features.
|
|
268
|
+
// Indeed otherwise it would create very different chunks depending on the geometry type
|
|
269
|
+
// (eg a bucket of 1000 polygons can actually contains a lot of points).
|
|
270
|
+
// const chunks = _.chunk(features, chunkSize)
|
|
271
|
+
const chunks = []
|
|
272
|
+
let chunkPoints = 0
|
|
273
|
+
let chunk = []
|
|
274
|
+
features.forEach(feature => {
|
|
275
|
+
const explodedFeature = explode(feature)
|
|
276
|
+
const nbPoints = explodedFeature.features.length
|
|
277
|
+
// Check if current chunk is not full
|
|
278
|
+
if ((chunkPoints + nbPoints) <= chunkSize) {
|
|
279
|
+
chunkPoints += nbPoints
|
|
280
|
+
chunk.push(feature)
|
|
281
|
+
} else {
|
|
282
|
+
// Otherwise push current chunk if not empty
|
|
283
|
+
if (chunk.length > 0) {
|
|
284
|
+
chunks.push(chunk)
|
|
285
|
+
}
|
|
286
|
+
// Then start new chunk
|
|
287
|
+
chunk = [feature]
|
|
288
|
+
chunkPoints = nbPoints
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
// Push last chunk
|
|
292
|
+
if (chunk.length > 0) {
|
|
293
|
+
chunks.push(chunk)
|
|
294
|
+
}
|
|
295
|
+
// Write the chunks
|
|
296
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
297
|
+
await api.getService('features').create(chunks[i])
|
|
298
|
+
if (typeof processCallback === 'function') await processCallback(i, chunks[i])
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export async function editFeaturesGeometry (geoJson) {
|
|
304
|
+
const features = (geoJson.type === 'FeatureCollection' ? geoJson.features : [geoJson])
|
|
305
|
+
const updatedFeatures = []
|
|
306
|
+
for (let i = 0; i < features.length; i++) {
|
|
307
|
+
const feature = features[i]
|
|
308
|
+
if (feature._id) {
|
|
309
|
+
const updatedFeature = await api.getService('features').patch(feature._id, _.pick(feature, ['geometry']))
|
|
310
|
+
updatedFeatures.push(updatedFeature)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return (geoJson.type === 'FeatureCollection' ? Object.assign(geoJson, { features: updatedFeatures }) : updatedFeatures)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export async function editFeaturesProperties (geoJson) {
|
|
317
|
+
const features = (geoJson.type === 'FeatureCollection' ? geoJson.features : [geoJson])
|
|
318
|
+
const updatedFeatures = []
|
|
319
|
+
for (let i = 0; i < features.length; i++) {
|
|
320
|
+
const feature = features[i]
|
|
321
|
+
if (feature._id) {
|
|
322
|
+
const updatedFeature = await api.getService('features').patch(feature._id, _.pick(feature, ['properties']))
|
|
323
|
+
updatedFeatures.push(updatedFeature)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return (geoJson.type === 'FeatureCollection' ? Object.assign(geoJson, { features: updatedFeatures }) : updatedFeatures)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export async function removeFeatures (geoJsonOrLayerId) {
|
|
330
|
+
// Remove all features of a given layer
|
|
331
|
+
if (typeof geoJsonOrLayerId === 'string') {
|
|
332
|
+
await api.getService('features').remove(null, { query: { layer: geoJsonOrLayerId } })
|
|
333
|
+
} else {
|
|
334
|
+
const features = (geoJsonOrLayerId.type === 'FeatureCollection' ? geoJsonOrLayerId.features : [geoJsonOrLayerId])
|
|
335
|
+
for (let i = 0; i < features.length; i++) {
|
|
336
|
+
const feature = features[i]
|
|
337
|
+
if (feature._id) await api.getService('features').remove(feature._id)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export async function fetchGeoJson (dataSource, options = {}) {
|
|
343
|
+
const response = await fetch(dataSource)
|
|
344
|
+
if (response.status !== 200) {
|
|
345
|
+
throw new Error(`Impossible to fetch ${dataSource}: ` + response.status)
|
|
346
|
+
}
|
|
347
|
+
const data = await response.json()
|
|
348
|
+
if (options.processor) processFeatures(data, options.processor)
|
|
349
|
+
if (options.transform) transformFeatures(data, options.transform)
|
|
350
|
+
return data
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function getFeatureStyleType (feature) {
|
|
354
|
+
const geometryType = _.get(feature, 'geometry.type')
|
|
355
|
+
if (!geometryType) {
|
|
356
|
+
logger.warn('[KDK] feature has undefined geometry')
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
if (geometryType === 'Point') return 'point'
|
|
360
|
+
if (['LineString', 'MultiLineString'].includes(geometryType)) return 'line'
|
|
361
|
+
if (['Polygon', 'MultiPolygon'].includes(geometryType)) return 'polygon'
|
|
362
|
+
logger.warn(`[KDK] unsupported geometry of type of ${geometryType}`)
|
|
363
|
+
return
|
|
364
|
+
}
|
|
@@ -1,78 +1,6 @@
|
|
|
1
1
|
import _ from 'lodash'
|
|
2
|
-
import rhumbBearing from '@turf/rhumb-bearing'
|
|
3
|
-
import rhumbDistance from '@turf/rhumb-distance'
|
|
4
|
-
import rotate from '@turf/transform-rotate'
|
|
5
|
-
import scale from '@turf/transform-scale'
|
|
6
|
-
import translate from '@turf/transform-translate'
|
|
7
2
|
import chroma from 'chroma-js'
|
|
8
|
-
import config from 'config'
|
|
9
3
|
import formatcoords from 'formatcoords'
|
|
10
|
-
import { buildUrl } from '../../../core/common/index.js'
|
|
11
|
-
import { api, Store } from '../../../core/client/index.js'
|
|
12
|
-
|
|
13
|
-
// Build a color map from a JS object specification
|
|
14
|
-
export function buildColorMap (options) {
|
|
15
|
-
let colorMap
|
|
16
|
-
const classes = _.get(options, 'classes')
|
|
17
|
-
const domain = _.get(options, 'domain')
|
|
18
|
-
const scale = _.get(options, 'scale')
|
|
19
|
-
const invert = _.get(options, 'invertScale')
|
|
20
|
-
if (scale) {
|
|
21
|
-
if (classes) {
|
|
22
|
-
colorMap = chroma.scale(scale).classes(invert ? classes.slice().reverse() : classes)
|
|
23
|
-
} else if (domain) {
|
|
24
|
-
colorMap = chroma.scale(scale).domain(invert ? domain.slice().reverse() : domain)
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return colorMap
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function transformFeatures (features, transform) {
|
|
31
|
-
features.forEach(feature => {
|
|
32
|
-
const scaling = _.get(transform, 'scale')
|
|
33
|
-
const rotation = _.get(transform, 'rotate')
|
|
34
|
-
const translation = _.get(transform, 'translate')
|
|
35
|
-
if (scaling) {
|
|
36
|
-
scale(feature, scaling.factor,
|
|
37
|
-
Object.assign(_.omit(scaling, ['factor']), { mutate: true }))
|
|
38
|
-
}
|
|
39
|
-
if (rotation) {
|
|
40
|
-
rotate(feature, rotation.angle,
|
|
41
|
-
Object.assign(_.omit(rotation, ['angle']), { mutate: true }))
|
|
42
|
-
}
|
|
43
|
-
if (translation) {
|
|
44
|
-
// Could be expressed as direction/distance or target point
|
|
45
|
-
// Take care that turfjs uses a rhumb line
|
|
46
|
-
if (translation.point) {
|
|
47
|
-
translation.distance = rhumbDistance(translation.pivot || [0, 0], translation.point)
|
|
48
|
-
translation.direction = rhumbBearing(translation.pivot || [0, 0], translation.point)
|
|
49
|
-
delete translation.pivot
|
|
50
|
-
delete translation.point
|
|
51
|
-
}
|
|
52
|
-
translate(feature, translation.distance, translation.direction,
|
|
53
|
-
Object.assign(_.omit(translation, ['direction', 'distance']), { mutate: true }))
|
|
54
|
-
}
|
|
55
|
-
})
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export async function fetchGeoJson (dataSource, options = {}) {
|
|
59
|
-
const response = await fetch(dataSource)
|
|
60
|
-
if (response.status !== 200) {
|
|
61
|
-
throw new Error(`Impossible to fetch ${dataSource}: ` + response.status)
|
|
62
|
-
}
|
|
63
|
-
const data = await response.json()
|
|
64
|
-
const features = (data.type === 'FeatureCollection' ? data.features : [data])
|
|
65
|
-
if (typeof options.processor === 'function') {
|
|
66
|
-
features.forEach(feature => options.processor(feature))
|
|
67
|
-
} else if (typeof options.processor === 'string') {
|
|
68
|
-
const compiler = _.template(options.processor)
|
|
69
|
-
features.forEach(feature => compiler({ feature, properties: feature.properties }))
|
|
70
|
-
}
|
|
71
|
-
if (options.transform) {
|
|
72
|
-
transformFeatures(features, options.transform)
|
|
73
|
-
}
|
|
74
|
-
return data
|
|
75
|
-
}
|
|
76
4
|
|
|
77
5
|
// Find the nearest time of a given one in a given moment time list
|
|
78
6
|
export function getNearestTime (time, times) {
|
|
@@ -106,72 +34,6 @@ export function getTimeInterval (times, mode = 'minimum') {
|
|
|
106
34
|
return interval
|
|
107
35
|
}
|
|
108
36
|
|
|
109
|
-
// Format (reverse) geocoding output
|
|
110
|
-
export function formatGeocodingResult (element) {
|
|
111
|
-
let label = element.formattedAddress || ''
|
|
112
|
-
if (!label) {
|
|
113
|
-
if (element.streetNumber) label += (element.streetNumber + ', ')
|
|
114
|
-
if (element.streetName) label += (element.streetName + ' ')
|
|
115
|
-
if (element.city) label += (element.city + ' ')
|
|
116
|
-
if (element.zipcode) label += (' (' + element.zipcode + ')')
|
|
117
|
-
}
|
|
118
|
-
return label
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Helper to set a JWT as query param in a target URL
|
|
122
|
-
export function setUrlJwt (item, path, baseUrl, jwtField, jwt) {
|
|
123
|
-
const url = _.get(item, path)
|
|
124
|
-
if (!url) return
|
|
125
|
-
// Check it conforms to required base URL
|
|
126
|
-
if (!url.startsWith(baseUrl)) return
|
|
127
|
-
// FIXME: specific case of Cesium OpenStreetMap provider
|
|
128
|
-
// Because Cesium generates the final url as base url + tile scheme + extension
|
|
129
|
-
// updating the base url property breaks it, for now we modify the extension
|
|
130
|
-
if ((path === 'cesium.url') && _.get(item, 'cesium.type') === 'OpenStreetMap') {
|
|
131
|
-
const ext = _.get(item, 'cesium.fileExtension', 'png')
|
|
132
|
-
_.set(item, 'cesium.fileExtension', ext + `?${jwtField}=${jwt}`)
|
|
133
|
-
} else {
|
|
134
|
-
_.set(item, path, buildUrl(url, { [jwtField]: jwt }))
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Helper to set required JWT as query param in a given set of layers for underlying engine
|
|
139
|
-
export async function setEngineJwt (layers) {
|
|
140
|
-
// If we need to use API gateway forward token as query parameter
|
|
141
|
-
// (Leaflet does not support anything else by default as it mainly uses raw <img> tags)
|
|
142
|
-
let jwt = (config.gatewayJwt ? await api.get('storage').getItem(config.gatewayJwt) : null)
|
|
143
|
-
let jwtField = config.gatewayJwtField
|
|
144
|
-
// Check both the default built-in config or the server provided one if any (eg mobile apps)
|
|
145
|
-
const gatewayUrl = Store.get('capabilities.api.gateway', config.gateway)
|
|
146
|
-
if (jwt) {
|
|
147
|
-
layers.forEach(layer => {
|
|
148
|
-
setUrlJwt(layer, 'iconUrl', gatewayUrl, jwtField, jwt)
|
|
149
|
-
setUrlJwt(layer, 'leaflet.source', gatewayUrl, jwtField, jwt)
|
|
150
|
-
setUrlJwt(layer, 'opendap.url', gatewayUrl, jwtField, jwt)
|
|
151
|
-
setUrlJwt(layer, 'geotiff.url', gatewayUrl, jwtField, jwt)
|
|
152
|
-
setUrlJwt(layer, 'wfs.url', gatewayUrl, jwtField, jwt)
|
|
153
|
-
setUrlJwt(layer, 'wcs.url', gatewayUrl, jwtField, jwt)
|
|
154
|
-
setUrlJwt(layer, 'cesium.url', gatewayUrl, jwtField, jwt)
|
|
155
|
-
setUrlJwt(layer, 'cesium.source', gatewayUrl, jwtField, jwt)
|
|
156
|
-
})
|
|
157
|
-
}
|
|
158
|
-
// We might also proxy some data directly from the app when using object storage
|
|
159
|
-
// This is only for raw raster data not OGC protocols
|
|
160
|
-
jwt = (config.apiJwt ? await api.get('storage').getItem(config.apiJwt) : null)
|
|
161
|
-
jwtField = 'jwt'
|
|
162
|
-
const apiUrl = api.getBaseUrl()
|
|
163
|
-
if (jwt) {
|
|
164
|
-
layers.forEach(layer => {
|
|
165
|
-
setUrlJwt(layer, 'geotiff.url', apiUrl, jwtField, jwt)
|
|
166
|
-
})
|
|
167
|
-
// We also allow absolute URLs for app like /api/storage/xxx
|
|
168
|
-
layers.forEach(layer => {
|
|
169
|
-
setUrlJwt(layer, 'geotiff.url', '/', jwtField, jwt)
|
|
170
|
-
})
|
|
171
|
-
}
|
|
172
|
-
return layers
|
|
173
|
-
}
|
|
174
|
-
|
|
175
37
|
export function getFeatureId (feature, layer) {
|
|
176
38
|
let featureId = _.get(layer, 'featureId')
|
|
177
39
|
// We need at least an internal ID to uniquely identify features for updates
|
|
@@ -213,16 +75,3 @@ export function coordinatesToGeoJSON (lat, lon, format, options) {
|
|
|
213
75
|
}
|
|
214
76
|
}
|
|
215
77
|
}
|
|
216
|
-
|
|
217
|
-
export function parseCoordinates (str) {
|
|
218
|
-
const coords = _.split(_.trim(str), ',')
|
|
219
|
-
if (coords.length !== 2) return
|
|
220
|
-
const latitude = Number(coords[0])
|
|
221
|
-
if (_.isNaN(latitude)) return
|
|
222
|
-
const longitude = Number(coords[1])
|
|
223
|
-
if (_.isNaN(longitude)) return
|
|
224
|
-
return {
|
|
225
|
-
latitude,
|
|
226
|
-
longitude
|
|
227
|
-
}
|
|
228
|
-
}
|