@kalisio/kdk 2.1.9 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.travis.test.sh +42 -10
- package/README.md +2 -2
- package/core/api/application.js +6 -1
- 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/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 +43 -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 +26 -20
- 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 +141 -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 +149 -4
- 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/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 +31 -10
- 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 +3 -0
- package/map/client/cesium/utils/utils.events.js +30 -0
- package/map/client/cesium/utils/utils.popup.js +17 -0
- package/map/client/cesium/{utils.js → utils/utils.style.js} +53 -49
- 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 +256 -430
- 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 -23
- package/map/client/components/catalog/KAddLayer.vue +3 -4
- package/map/client/components/catalog/KConnectLayer.vue +16 -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 +1 -1
- package/map/client/components/legend/KLayerLegend.vue +61 -0
- package/map/client/components/legend/KLegend.vue +45 -44
- 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/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 -23
- package/map/client/composables/activity.js +15 -2
- package/map/client/composables/catalog.js +66 -0
- package/map/client/composables/highlight.js +56 -20
- package/map/client/composables/index.js +2 -0
- package/map/client/composables/location.js +25 -18
- package/map/client/composables/project.js +122 -0
- package/map/client/geolocation.js +1 -1
- package/map/client/globe.js +2 -0
- package/map/client/i18n/map_en.json +123 -76
- package/map/client/i18n/map_fr.json +124 -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 +40 -0
- package/map/client/leaflet/TiledFeatureLayer.js +1 -1
- package/map/client/leaflet/TiledMeshLayer.js +11 -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 +191 -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 +29 -21
- package/map/client/mixins/globe/mixin.geojson-layers.js +132 -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 +13 -11
- 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 +56 -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 +66 -191
- 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 +58 -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 +149 -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 +174 -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 +297 -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/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 +25 -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/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
|
@@ -1,78 +1,6 @@
|
|
|
1
1
|
import _ from 'lodash'
|
|
2
|
-
import rhumbBearing from '@turf/rhumb-bearing'
|
|
3
|
-
import rhumbDistance from '@turf/rhumb-distance'
|
|
4
|
-
import rotate from '@turf/transform-rotate'
|
|
5
|
-
import scale from '@turf/transform-scale'
|
|
6
|
-
import translate from '@turf/transform-translate'
|
|
7
2
|
import chroma from 'chroma-js'
|
|
8
|
-
import config from 'config'
|
|
9
3
|
import formatcoords from 'formatcoords'
|
|
10
|
-
import { buildUrl } from '../../../core/common/index.js'
|
|
11
|
-
import { api, Store } from '../../../core/client/index.js'
|
|
12
|
-
|
|
13
|
-
// Build a color map from a JS object specification
|
|
14
|
-
export function buildColorMap (options) {
|
|
15
|
-
let colorMap
|
|
16
|
-
const classes = _.get(options, 'classes')
|
|
17
|
-
const domain = _.get(options, 'domain')
|
|
18
|
-
const scale = _.get(options, 'scale')
|
|
19
|
-
const invert = _.get(options, 'invertScale')
|
|
20
|
-
if (scale) {
|
|
21
|
-
if (classes) {
|
|
22
|
-
colorMap = chroma.scale(scale).classes(invert ? classes.slice().reverse() : classes)
|
|
23
|
-
} else if (domain) {
|
|
24
|
-
colorMap = chroma.scale(scale).domain(invert ? domain.slice().reverse() : domain)
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return colorMap
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function transformFeatures (features, transform) {
|
|
31
|
-
features.forEach(feature => {
|
|
32
|
-
const scaling = _.get(transform, 'scale')
|
|
33
|
-
const rotation = _.get(transform, 'rotate')
|
|
34
|
-
const translation = _.get(transform, 'translate')
|
|
35
|
-
if (scaling) {
|
|
36
|
-
scale(feature, scaling.factor,
|
|
37
|
-
Object.assign(_.omit(scaling, ['factor']), { mutate: true }))
|
|
38
|
-
}
|
|
39
|
-
if (rotation) {
|
|
40
|
-
rotate(feature, rotation.angle,
|
|
41
|
-
Object.assign(_.omit(rotation, ['angle']), { mutate: true }))
|
|
42
|
-
}
|
|
43
|
-
if (translation) {
|
|
44
|
-
// Could be expressed as direction/distance or target point
|
|
45
|
-
// Take care that turfjs uses a rhumb line
|
|
46
|
-
if (translation.point) {
|
|
47
|
-
translation.distance = rhumbDistance(translation.pivot || [0, 0], translation.point)
|
|
48
|
-
translation.direction = rhumbBearing(translation.pivot || [0, 0], translation.point)
|
|
49
|
-
delete translation.pivot
|
|
50
|
-
delete translation.point
|
|
51
|
-
}
|
|
52
|
-
translate(feature, translation.distance, translation.direction,
|
|
53
|
-
Object.assign(_.omit(translation, ['direction', 'distance']), { mutate: true }))
|
|
54
|
-
}
|
|
55
|
-
})
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export async function fetchGeoJson (dataSource, options = {}) {
|
|
59
|
-
const response = await fetch(dataSource)
|
|
60
|
-
if (response.status !== 200) {
|
|
61
|
-
throw new Error(`Impossible to fetch ${dataSource}: ` + response.status)
|
|
62
|
-
}
|
|
63
|
-
const data = await response.json()
|
|
64
|
-
const features = (data.type === 'FeatureCollection' ? data.features : [data])
|
|
65
|
-
if (typeof options.processor === 'function') {
|
|
66
|
-
features.forEach(feature => options.processor(feature))
|
|
67
|
-
} else if (typeof options.processor === 'string') {
|
|
68
|
-
const compiler = _.template(options.processor)
|
|
69
|
-
features.forEach(feature => compiler({ feature, properties: feature.properties }))
|
|
70
|
-
}
|
|
71
|
-
if (options.transform) {
|
|
72
|
-
transformFeatures(features, options.transform)
|
|
73
|
-
}
|
|
74
|
-
return data
|
|
75
|
-
}
|
|
76
4
|
|
|
77
5
|
// Find the nearest time of a given one in a given moment time list
|
|
78
6
|
export function getNearestTime (time, times) {
|
|
@@ -106,72 +34,6 @@ export function getTimeInterval (times, mode = 'minimum') {
|
|
|
106
34
|
return interval
|
|
107
35
|
}
|
|
108
36
|
|
|
109
|
-
// Format (reverse) geocoding output
|
|
110
|
-
export function formatGeocodingResult (element) {
|
|
111
|
-
let label = element.formattedAddress || ''
|
|
112
|
-
if (!label) {
|
|
113
|
-
if (element.streetNumber) label += (element.streetNumber + ', ')
|
|
114
|
-
if (element.streetName) label += (element.streetName + ' ')
|
|
115
|
-
if (element.city) label += (element.city + ' ')
|
|
116
|
-
if (element.zipcode) label += (' (' + element.zipcode + ')')
|
|
117
|
-
}
|
|
118
|
-
return label
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Helper to set a JWT as query param in a target URL
|
|
122
|
-
export function setUrlJwt (item, path, baseUrl, jwtField, jwt) {
|
|
123
|
-
const url = _.get(item, path)
|
|
124
|
-
if (!url) return
|
|
125
|
-
// Check it conforms to required base URL
|
|
126
|
-
if (!url.startsWith(baseUrl)) return
|
|
127
|
-
// FIXME: specific case of Cesium OpenStreetMap provider
|
|
128
|
-
// Because Cesium generates the final url as base url + tile scheme + extension
|
|
129
|
-
// updating the base url property breaks it, for now we modify the extension
|
|
130
|
-
if ((path === 'cesium.url') && _.get(item, 'cesium.type') === 'OpenStreetMap') {
|
|
131
|
-
const ext = _.get(item, 'cesium.fileExtension', 'png')
|
|
132
|
-
_.set(item, 'cesium.fileExtension', ext + `?${jwtField}=${jwt}`)
|
|
133
|
-
} else {
|
|
134
|
-
_.set(item, path, buildUrl(url, { [jwtField]: jwt }))
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Helper to set required JWT as query param in a given set of layers for underlying engine
|
|
139
|
-
export async function setEngineJwt (layers) {
|
|
140
|
-
// If we need to use API gateway forward token as query parameter
|
|
141
|
-
// (Leaflet does not support anything else by default as it mainly uses raw <img> tags)
|
|
142
|
-
let jwt = (config.gatewayJwt ? await api.get('storage').getItem(config.gatewayJwt) : null)
|
|
143
|
-
let jwtField = config.gatewayJwtField
|
|
144
|
-
// Check both the default built-in config or the server provided one if any (eg mobile apps)
|
|
145
|
-
const gatewayUrl = Store.get('capabilities.api.gateway', config.gateway)
|
|
146
|
-
if (jwt) {
|
|
147
|
-
layers.forEach(layer => {
|
|
148
|
-
setUrlJwt(layer, 'iconUrl', gatewayUrl, jwtField, jwt)
|
|
149
|
-
setUrlJwt(layer, 'leaflet.source', gatewayUrl, jwtField, jwt)
|
|
150
|
-
setUrlJwt(layer, 'opendap.url', gatewayUrl, jwtField, jwt)
|
|
151
|
-
setUrlJwt(layer, 'geotiff.url', gatewayUrl, jwtField, jwt)
|
|
152
|
-
setUrlJwt(layer, 'wfs.url', gatewayUrl, jwtField, jwt)
|
|
153
|
-
setUrlJwt(layer, 'wcs.url', gatewayUrl, jwtField, jwt)
|
|
154
|
-
setUrlJwt(layer, 'cesium.url', gatewayUrl, jwtField, jwt)
|
|
155
|
-
setUrlJwt(layer, 'cesium.source', gatewayUrl, jwtField, jwt)
|
|
156
|
-
})
|
|
157
|
-
}
|
|
158
|
-
// We might also proxy some data directly from the app when using object storage
|
|
159
|
-
// This is only for raw raster data not OGC protocols
|
|
160
|
-
jwt = (config.apiJwt ? await api.get('storage').getItem(config.apiJwt) : null)
|
|
161
|
-
jwtField = 'jwt'
|
|
162
|
-
const apiUrl = api.getBaseUrl()
|
|
163
|
-
if (jwt) {
|
|
164
|
-
layers.forEach(layer => {
|
|
165
|
-
setUrlJwt(layer, 'geotiff.url', apiUrl, jwtField, jwt)
|
|
166
|
-
})
|
|
167
|
-
// We also allow absolute URLs for app like /api/storage/xxx
|
|
168
|
-
layers.forEach(layer => {
|
|
169
|
-
setUrlJwt(layer, 'geotiff.url', '/', jwtField, jwt)
|
|
170
|
-
})
|
|
171
|
-
}
|
|
172
|
-
return layers
|
|
173
|
-
}
|
|
174
|
-
|
|
175
37
|
export function getFeatureId (feature, layer) {
|
|
176
38
|
let featureId = _.get(layer, 'featureId')
|
|
177
39
|
// We need at least an internal ID to uniquely identify features for updates
|
|
@@ -213,16 +75,3 @@ export function coordinatesToGeoJSON (lat, lon, format, options) {
|
|
|
213
75
|
}
|
|
214
76
|
}
|
|
215
77
|
}
|
|
216
|
-
|
|
217
|
-
export function parseCoordinates (str) {
|
|
218
|
-
const coords = _.split(_.trim(str), ',')
|
|
219
|
-
if (coords.length !== 2) return
|
|
220
|
-
const latitude = Number(coords[0])
|
|
221
|
-
if (_.isNaN(latitude)) return
|
|
222
|
-
const longitude = Number(coords[1])
|
|
223
|
-
if (_.isNaN(longitude)) return
|
|
224
|
-
return {
|
|
225
|
-
latitude,
|
|
226
|
-
longitude
|
|
227
|
-
}
|
|
228
|
-
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import logger from 'loglevel'
|
|
3
|
+
import { Notify, Loading } from 'quasar'
|
|
4
|
+
import explode from '@turf/explode'
|
|
5
|
+
import { i18n, api, utils as kCoreUtils } from '../../../core/client/index.js'
|
|
6
|
+
import { checkFeatures, createFeatures, removeFeatures } from './utils.features.js'
|
|
7
|
+
|
|
8
|
+
export const InternalLayerProperties = ['actions', 'label', 'isVisible', 'isDisabled']
|
|
9
|
+
|
|
10
|
+
export function isInMemoryLayer (layer) {
|
|
11
|
+
return layer._id === undefined
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isUserLayer (layer) {
|
|
15
|
+
return (_.get(layer, 'scope') === 'user')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function isFeatureLayer (layer) {
|
|
19
|
+
return (_.get(layer, 'service') === 'features')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function hasFeatureSchema (layer) {
|
|
23
|
+
return _.has(layer, 'schema')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function isLayerSelectable (layer) {
|
|
27
|
+
return _.get(layer, 'isSelectable', true)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isLayerProbable (layer) {
|
|
31
|
+
return _.get(layer, 'isProbable', false)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isLayerStorable (layer) {
|
|
35
|
+
// Only possible when not saved by default
|
|
36
|
+
if (layer._id) return false
|
|
37
|
+
return _.get(layer, 'isStorable', isUserLayer(layer))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function isLayerEditable (layer) {
|
|
41
|
+
// Only possible when saved by default
|
|
42
|
+
if (!layer._id) return false
|
|
43
|
+
return _.get(layer, 'isEditable', isUserLayer(layer))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function isLayerRemovable (layer) {
|
|
47
|
+
return _.get(layer, 'isRemovable', isUserLayer(layer))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function isLayerStyleEditable (layer) {
|
|
51
|
+
return _.get(layer, 'isStyleEditable', isUserLayer(layer))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function isLayerDataEditable (layer) {
|
|
55
|
+
return _.get(layer, 'isDataEditable', isUserLayer(layer) && isFeatureLayer(layer))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isTerrainLayer (layer) {
|
|
59
|
+
if (layer.type === 'TerrainLayer') return true
|
|
60
|
+
const cesiumOptions = layer.cesium || layer
|
|
61
|
+
return (cesiumOptions.type === 'Cesium') || (cesiumOptions.type === 'Ellipsoid')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function saveGeoJsonLayer (layer, geoJson, chunkSize = 5000) {
|
|
65
|
+
// Check for invalid features first
|
|
66
|
+
const check = checkFeatures(geoJson)
|
|
67
|
+
if (check.kinks.length > 0) {
|
|
68
|
+
const result = await kCoreUtils.dialog({
|
|
69
|
+
title: i18n.t('utils.layers.INVALID_FEATURES_DIALOG_TITLE', { total: check.kinks.length }),
|
|
70
|
+
message: i18n.t('utils.layers.INVALID_FEATURES_DIALOG_MESSAGE', { total: check.kinks.length }),
|
|
71
|
+
options: {
|
|
72
|
+
type: 'toggle',
|
|
73
|
+
model: [],
|
|
74
|
+
items: [
|
|
75
|
+
{ label: i18n.t('utils.layers.DOWNLOAD_INVALID_FEATURES_LABEL'), value: 'download' }
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
html: true,
|
|
79
|
+
ok: {
|
|
80
|
+
label: i18n.t('OK'),
|
|
81
|
+
flat: true
|
|
82
|
+
},
|
|
83
|
+
cancel: {
|
|
84
|
+
label: i18n.t('CANCEL'),
|
|
85
|
+
flat: true
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
if (!result.ok) return
|
|
89
|
+
// Export invalid features if required
|
|
90
|
+
if (_.get(result, 'data', []).includes('download')) {
|
|
91
|
+
kCoreUtils.downloadAsBlob(JSON.stringify({ type: 'FeatureCollection', features: check.kinks }),
|
|
92
|
+
i18n.t('utils.layers.INVALID_FEATURES_FILE'), 'application/json;charset=utf-8;')
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Change data source from in-memory to features service
|
|
96
|
+
_.set(layer, 'service', 'features')
|
|
97
|
+
if (_.has(layer, 'leaflet')) _.set(layer, 'leaflet.source', '/api/features')
|
|
98
|
+
if (_.has(layer, 'cesium')) _.set(layer, 'cesium.source', '/api/features')
|
|
99
|
+
const features = (geoJson.type === 'FeatureCollection' ? geoJson.features : [geoJson])
|
|
100
|
+
// If too much data use tiling
|
|
101
|
+
// The threshold is based on the number of points and not features.
|
|
102
|
+
// Indeed otherwise the complexity will be different depending on the geometry type
|
|
103
|
+
// (eg a bucket of 1000 polygons can actually contains a lot of points).
|
|
104
|
+
let nbPoints = 0
|
|
105
|
+
features.forEach(feature => {
|
|
106
|
+
nbPoints += explode(feature).features.length
|
|
107
|
+
})
|
|
108
|
+
if (nbPoints > 5000) {
|
|
109
|
+
_.set(layer, 'leaflet.tiled', true)
|
|
110
|
+
_.set(layer, 'leaflet.minZoom', 15)
|
|
111
|
+
}
|
|
112
|
+
Loading.show({ message: i18n.t('utils.layers.SAVING_LABEL', { processed: 0, total: features.length }), html: true })
|
|
113
|
+
let createdLayer
|
|
114
|
+
try {
|
|
115
|
+
createdLayer = await api.getService('catalog')
|
|
116
|
+
.create(_.omit(layer, InternalLayerProperties))
|
|
117
|
+
let nbFeatures = 0
|
|
118
|
+
// We use the generated DB ID as layer ID on features
|
|
119
|
+
await createFeatures(geoJson, createdLayer._id, chunkSize, (i, chunk) => {
|
|
120
|
+
// Update saving message according to new chunk data
|
|
121
|
+
nbFeatures += chunk.length
|
|
122
|
+
Loading.show({
|
|
123
|
+
message: i18n.t('utils.layers.SAVING_LABEL', { processed: nbFeatures, total: features.length }),
|
|
124
|
+
html: true
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
// Because we save all features in a single service use filtering to separate layers
|
|
128
|
+
createdLayer = await api.getService('catalog').patch(createdLayer._id, { baseQuery: { layer: createdLayer._id } })
|
|
129
|
+
if (_.get(layer, 'leaflet.tiled')) {
|
|
130
|
+
Notify.create({ type: 'positive', message: i18n.t('utils.layers.SAVE_DIALOG_MESSAGE'), timeout: 10000, html: true })
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
// User error message on operation should be raised by error hook, otherwise this is more a coding error
|
|
134
|
+
logger.error(`[KDK] ${error}`)
|
|
135
|
+
}
|
|
136
|
+
Loading.hide()
|
|
137
|
+
return createdLayer
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function saveLayer (layer) {
|
|
141
|
+
await api.getService('catalog').create(_.omit(layer, InternalLayerProperties))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function removeLayer (layer) {
|
|
145
|
+
const result = await kCoreUtils.dialog({
|
|
146
|
+
title: i18n.t('utils.layers.REMOVE_DIALOG_TITLE', { layer: layer.label || layer.name }),
|
|
147
|
+
message: i18n.t('utils.layers.REMOVE_DIALOG_MESSAGE', { layer: layer.label || layer.name }),
|
|
148
|
+
html: true,
|
|
149
|
+
ok: {
|
|
150
|
+
label: i18n.t('OK'),
|
|
151
|
+
flat: true
|
|
152
|
+
},
|
|
153
|
+
cancel: {
|
|
154
|
+
label: i18n.t('CANCEL'),
|
|
155
|
+
flat: true
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
if (!result.ok) return false
|
|
159
|
+
Loading.show({ message: i18n.t('utils.layers.REMOVING_LABEL'), html: true })
|
|
160
|
+
try {
|
|
161
|
+
if (layer._id) {
|
|
162
|
+
// If persistent feature layer remove features as well
|
|
163
|
+
if (isFeatureLayer(layer)) {
|
|
164
|
+
await removeFeatures(layer._id)
|
|
165
|
+
}
|
|
166
|
+
await api.getService('catalog').remove(layer._id)
|
|
167
|
+
}
|
|
168
|
+
} catch (error) {
|
|
169
|
+
// User error message on operation should be raised by error hook, otherwise this is more a coding error
|
|
170
|
+
logger.error(`[KDK] ${error}`)
|
|
171
|
+
}
|
|
172
|
+
Loading.hide()
|
|
173
|
+
return true
|
|
174
|
+
}
|
|
@@ -1,7 +1,59 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import logger from 'loglevel'
|
|
3
|
+
import config from 'config'
|
|
4
|
+
import { Store, api, i18n, Events } from '../../../core.client.js'
|
|
5
|
+
import { formatUserCoordinates } from './utils.js'
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
// Format (reverse) geocoding output
|
|
8
|
+
function formatGeocodingResult (result) {
|
|
9
|
+
const properties = result.properties
|
|
10
|
+
if (!properties) {
|
|
11
|
+
logger.warn('[KDK] invalid geocoding result: missing \'properties\' property')
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
// check whether the result as a valid formatted address
|
|
15
|
+
let label = properties.formattedAddress || ''
|
|
16
|
+
// try to build a formatted address
|
|
17
|
+
if (!label) {
|
|
18
|
+
if (properties.streetNumber) label += (properties.streetNumber + ', ')
|
|
19
|
+
if (properties.streetName) label += (properties.streetName + ' ')
|
|
20
|
+
if (properties.city) label += (properties.city + ' ')
|
|
21
|
+
if (properties.zipcode) label += (' (' + properties.zipcode + ')')
|
|
22
|
+
}
|
|
23
|
+
// otherwise retireve the match prop
|
|
24
|
+
if (!label) {
|
|
25
|
+
if (!_.has(result, 'geokoder.matchProp')) {
|
|
26
|
+
logger.warn('[KDK] invalid geocoding result: missing \'geokoder.matchProp\' property')
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
label = _.get(result, result.geokoder.matchProp, '')
|
|
30
|
+
}
|
|
31
|
+
return label
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function parseCoordinates (str) {
|
|
35
|
+
const coords = _.split(_.trim(str), ',')
|
|
36
|
+
if (coords.length !== 2) return
|
|
37
|
+
const latitude = Number(coords[0])
|
|
38
|
+
if (_.isNaN(latitude)) return
|
|
39
|
+
const longitude = Number(coords[1])
|
|
40
|
+
if (_.isNaN(longitude)) return
|
|
41
|
+
return {
|
|
42
|
+
latitude,
|
|
43
|
+
longitude
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function queryGeocoder(planetConfig, path, query = '') {
|
|
48
|
+
const endpoint = planetConfig.gateway + '/geocoder'
|
|
49
|
+
const jwt = await api.get('storage').getItem(planetConfig.gatewayJwt)
|
|
50
|
+
let url = `${endpoint}/${path}`
|
|
51
|
+
if (query) url += `?${query}`
|
|
52
|
+
const results = await fetch(url, { headers: { Authorization: `Bearer ${jwt}` } }).then((response) => response.json())
|
|
53
|
+
return results
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function searchLocation (planetConfig, pattern, options) {
|
|
5
57
|
const locations = []
|
|
6
58
|
// Try to parse lat/long coordinates
|
|
7
59
|
const coordinates = parseCoordinates(pattern)
|
|
@@ -17,29 +69,45 @@ export async function searchLocation (pattern, options) {
|
|
|
17
69
|
}
|
|
18
70
|
})
|
|
19
71
|
} else {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
locations.push(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
coordinates: [element.longitude, element.latitude]
|
|
32
|
-
},
|
|
33
|
-
properties: {
|
|
34
|
-
name: formatGeocodingResult(element)
|
|
35
|
-
}
|
|
36
|
-
})
|
|
72
|
+
let filter = ''
|
|
73
|
+
if (!_.isEmpty(options.geocoders)) {
|
|
74
|
+
// only request geocoder results from specified sources
|
|
75
|
+
filter = '&sources=*(' + options.geocoders.join('|') + ')'
|
|
76
|
+
}
|
|
77
|
+
const results = await queryGeocoder(planetConfig, 'forward', `q=${pattern}${filter}`)
|
|
78
|
+
results.forEach(result => {
|
|
79
|
+
locations.push(
|
|
80
|
+
Object.assign(
|
|
81
|
+
_.pick(result, ['type', 'geometry']),
|
|
82
|
+
{ properties: { name: formatGeocodingResult(result), source: result.geokoder.source } }))
|
|
37
83
|
})
|
|
38
84
|
}
|
|
39
85
|
return locations
|
|
40
86
|
}
|
|
41
87
|
|
|
42
|
-
export async function listGeocoders () {
|
|
43
|
-
|
|
44
|
-
|
|
88
|
+
export async function listGeocoders (planetConfig) {
|
|
89
|
+
let response
|
|
90
|
+
try {
|
|
91
|
+
response = await queryGeocoder(planetConfig, 'capabilities/forward')
|
|
92
|
+
if (response.i18n) i18n.registerTranslation(response.i18n)
|
|
93
|
+
} catch (error) {
|
|
94
|
+
Events.emit('error', { message: i18n.t('errors.NETWORK_ERROR') })
|
|
95
|
+
}
|
|
96
|
+
return _.get(response, 'geocoders', [])
|
|
45
97
|
}
|
|
98
|
+
|
|
99
|
+
// Filter geocoder sources based on given project
|
|
100
|
+
export function filterGeocoders(geocoders, project) {
|
|
101
|
+
return geocoders.filter(geocoder => {
|
|
102
|
+
// Geocoder id can directly be a string or the value of UI elements with label/value
|
|
103
|
+
const id = geocoder.value || geocoder
|
|
104
|
+
if (project && id.startsWith('kano:')) {
|
|
105
|
+
const service = id.replace('kano:', '')
|
|
106
|
+
// Depending on the layer the geocoding source (i.e. collection/service) is not the same
|
|
107
|
+
let layer = _.find(project.layers, { service })
|
|
108
|
+
if (!layer) layer = _.find(project.layers, { probeService: service })
|
|
109
|
+
return layer
|
|
110
|
+
}
|
|
111
|
+
return true
|
|
112
|
+
})
|
|
113
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
|
|
3
|
+
export function getCatalogProjectQuery (project) {
|
|
4
|
+
// As we keep track of ID/name depending on if a layer is a user-defined one or not we need to process both
|
|
5
|
+
const idQuery = { _id: { $in: _.map(_.filter(project.layers, '_id'), '_id') } }
|
|
6
|
+
const nameQuery = { name: { $in: _.map(_.filter(project.layers, 'name'), 'name') } }
|
|
7
|
+
return { $or: [idQuery, nameQuery] }
|
|
8
|
+
}
|