@kalisio/kdk 2.1.5 → 2.1.6

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.
@@ -89,7 +89,7 @@ async function apply () {
89
89
  }
90
90
  }
91
91
  } else {
92
- $q.notify({ type: 'negative', message: i18n.t('KSendResetPassword.ERROR_INVALID_EMAIL') })
92
+ $q.notify({ type: 'negative', message: i18n.t('KSendResetPassword.ERROR_INVALID_EMAIL') })
93
93
  }
94
94
  send.value = true
95
95
  processing.value = false
@@ -75,9 +75,8 @@ async function onLogin () {
75
75
  } catch (error) {
76
76
  $q.notify({ type: 'negative', message: i18n.t('KLoginScreen.LOGIN_ERROR') })
77
77
  }
78
- }
79
- else {
80
- $q.notify({ type: 'negative', message: i18n.t('KLoginScreen.INVALID_EMAIL') })
78
+ } else {
79
+ $q.notify({ type: 'negative', message: i18n.t('KLoginScreen.INVALID_EMAIL') })
81
80
  }
82
81
  loading.value = false
83
82
  }
@@ -3,11 +3,11 @@
3
3
  <slot name="header" />
4
4
  <div v-if="layers.length > 0">
5
5
  <template v-for="layer in layers">
6
- <component
7
- :is="layerRenderer.component"
8
- v-bind="layerRenderer.options"
6
+ <component
7
+ :is="layerRenderer.component"
8
+ v-bind="layerRenderer.options"
9
9
  :layer="layer"
10
- @toggled="onLayerToggled"
10
+ @toggled="onLayerToggled"
11
11
  @filter-toggled="onLayerFilterToggled"
12
12
  />
13
13
  </template>
@@ -1,10 +1,10 @@
1
1
  <template>
2
2
  <q-list dense bordered>
3
3
  <div class="no-padding" :style="panelStyle">
4
- <KPanel
5
- id="favorite-views-toolbar"
6
- :content="toolbar"
7
- class="no-wrap q-pl-sm q-pr-md"
4
+ <KPanel
5
+ id="favorite-views-toolbar"
6
+ :content="toolbar"
7
+ class="no-wrap q-pl-sm q-pr-md"
8
8
  />
9
9
  <KColumn
10
10
  class="q-pl-sm"
@@ -29,7 +29,7 @@ import { Geolocation } from '../../geolocation'
29
29
  import {
30
30
  setEngineJwt, coordinatesToGeoJSON, formatUserCoordinates,
31
31
  bindLeafletEvents, unbindLeafletEvents, createLeafletMarkerFromStyle, convertToLeafletFromSimpleStyleSpec
32
- } from '../../utils'
32
+ } from '../../utils.map.js'
33
33
 
34
34
  export default {
35
35
  components: {
@@ -1,6 +1,6 @@
1
1
  import * as commonMixins from './mixins/index.js'
2
2
  import * as globeMixins from './mixins/globe/index.js'
3
- import * as utils from './utils.js'
3
+ import * as utils from './utils.globe.js'
4
4
  import init from './init.js'
5
5
 
6
6
  const mixins = Object.assign({}, commonMixins, { globe: globeMixins })
@@ -2,7 +2,7 @@ import * as composables from './composables/index.js'
2
2
  import * as commonMixins from './mixins/index.js'
3
3
  import * as mapMixins from './mixins/map/index.js'
4
4
  import * as globeMixins from './mixins/globe/index.js'
5
- import * as utils from './utils.js'
5
+ import * as utils from './utils.all.js'
6
6
  import * as elevationUtils from './elevation-utils.js'
7
7
  import init from './init.js'
8
8
  const mixins = Object.assign({}, commonMixins, { map: mapMixins, globe: globeMixins })
package/map/client/map.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as composables from './composables/index.js'
2
2
  import * as commonMixins from './mixins/index.js'
3
3
  import * as mapMixins from './mixins/map/index.js'
4
- import * as utils from './utils.js'
4
+ import * as utils from './utils.map.js'
5
5
  import init from './init.js'
6
6
 
7
7
  const mixins = Object.assign({}, commonMixins, { map: mapMixins })
@@ -8,7 +8,7 @@ import Cesium from 'cesium/Source/Cesium.js'
8
8
  import 'cesium/Source/Widgets/widgets.css'
9
9
  import BuildModuleUrl from 'cesium/Source/Core/buildModuleUrl.js'
10
10
  import { Geolocation } from '../../geolocation.js'
11
- import { convertCesiumHandlerEvent } from '../../utils.js'
11
+ import { convertCesiumHandlerEvent } from '../../utils.globe.js'
12
12
  // Cesium has its own dynamic module loader requiring to be configured
13
13
  // Cesium files need to be also added as static assets of the applciation
14
14
  BuildModuleUrl.setBaseUrl('/Cesium/')
@@ -1,6 +1,6 @@
1
1
  import _ from 'lodash'
2
2
  import { Time, Units } from '../../../../core/client/index.js'
3
- import { getTextTable } from '../../utils.js'
3
+ import { getTextTable } from '../../utils.globe.js'
4
4
 
5
5
  export const popup = {
6
6
  methods: {
@@ -1,7 +1,7 @@
1
1
  import Cesium from 'cesium/Source/Cesium.js'
2
2
  import _ from 'lodash'
3
3
  import chroma from 'chroma-js'
4
- import { convertToCesiumFromSimpleStyleSpec, convertToCesiumObjects, CesiumStyleMappings, CesiumEntityTypes } from '../../utils.js'
4
+ import { convertToCesiumFromSimpleStyleSpec, convertToCesiumObjects, CesiumStyleMappings, CesiumEntityTypes } from '../../utils.globe.js'
5
5
 
6
6
  export const style = {
7
7
  methods: {
@@ -28,7 +28,7 @@ import { getAppLocale } from '../../../../core/client/utils/index.js'
28
28
  import { uid } from 'quasar'
29
29
  import '../../leaflet/BoxSelection.js'
30
30
  import { Geolocation } from '../../geolocation.js'
31
- import { LeafletEvents, bindLeafletEvents, generatePropertiesSchema } from '../../utils.js' // https://github.com/socib/Leaflet.TimeDimension/issues/124
31
+ import { LeafletEvents, bindLeafletEvents, generatePropertiesSchema } from '../../utils.map.js' // https://github.com/socib/Leaflet.TimeDimension/issues/124
32
32
 
33
33
  import markerIcon from 'leaflet/dist/images/marker-icon.png'
34
34
  import retinaIcon from 'leaflet/dist/images/marker-icon-2x.png'
@@ -1,6 +1,6 @@
1
1
  import _ from 'lodash'
2
2
  import { CanvasDrawContext } from '../../canvas-draw-context.js'
3
- import { bindLeafletEvents } from '../../utils.js'
3
+ import { bindLeafletEvents } from '../../utils.map.js'
4
4
  import L from 'leaflet'
5
5
 
6
6
  // Helper function to forward events when click through is enabled
@@ -2,7 +2,7 @@ import _ from 'lodash'
2
2
  import L from 'leaflet'
3
3
  import { getType, getGeom } from '@turf/invariant'
4
4
  import { Dialog, uid } from 'quasar'
5
- import { bindLeafletEvents, unbindLeafletEvents } from '../../utils.js'
5
+ import { bindLeafletEvents, unbindLeafletEvents } from '../../utils.map.js'
6
6
 
7
7
  // Events we listen while layer is in edition mode
8
8
  const mapEditEvents = ['pm:create']
@@ -7,7 +7,7 @@ import { Time } from '../../../../core/client/time.js'
7
7
  import { GradientPath } from '../../leaflet/GradientPath.js'
8
8
  import { MaskLayer } from '../../leaflet/MaskLayer.js'
9
9
  import { TiledFeatureLayer } from '../../leaflet/TiledFeatureLayer.js'
10
- import { fetchGeoJson, LeafletEvents, bindLeafletEvents, unbindLeafletEvents, getFeatureId } from '../../utils.js'
10
+ import { fetchGeoJson, LeafletEvents, bindLeafletEvents, unbindLeafletEvents, getFeatureId } from '../../utils.map.js'
11
11
  import * as wfs from '../../../common/wfs-utils.js'
12
12
 
13
13
  // Override default remove handler for leaflet-realtime due to
@@ -1,6 +1,6 @@
1
1
  import _ from 'lodash'
2
2
  import logger from 'loglevel'
3
- import { LeafletEvents, bindLeafletEvents } from '../../utils.js'
3
+ import { LeafletEvents, bindLeafletEvents } from '../../utils.map.js'
4
4
 
5
5
  export const mapillaryLayers = {
6
6
  methods: {
@@ -1,7 +1,7 @@
1
1
  import L from 'leaflet'
2
2
  import _ from 'lodash'
3
3
  import { Time, Units } from '../../../../core/client/index.js'
4
- import { getHtmlTable } from '../../utils.js'
4
+ import { getHtmlTable } from '../../utils.map.js'
5
5
 
6
6
  export const popup = {
7
7
  methods: {
@@ -1,7 +1,7 @@
1
1
  import L from 'leaflet'
2
2
  import _ from 'lodash'
3
3
  import chroma from 'chroma-js'
4
- import { createLeafletMarkerFromStyle, convertToLeafletFromSimpleStyleSpec, LeafletStyleMappings } from '../../utils.js'
4
+ import { createLeafletMarkerFromStyle, convertToLeafletFromSimpleStyleSpec, LeafletStyleMappings } from '../../utils.map.js'
5
5
 
6
6
  export const style = {
7
7
  methods: {
@@ -0,0 +1,3 @@
1
+ export * from './utils.js'
2
+ export * from './utils.location.js'
3
+ export * from './utils.schema.js'
@@ -0,0 +1,228 @@
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
+ import chroma from 'chroma-js'
8
+ import config from 'config'
9
+ 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
+
77
+ // Find the nearest time of a given one in a given moment time list
78
+ export function getNearestTime (time, times) {
79
+ // Look for the nearest time
80
+ let timeIndex = -1
81
+ let minDiff = Infinity
82
+ times.forEach((currentTime, index) => {
83
+ const diff = Math.abs(time.diff(currentTime))
84
+ if (diff < minDiff) {
85
+ minDiff = diff
86
+ timeIndex = index
87
+ }
88
+ })
89
+ return { index: timeIndex, difference: minDiff }
90
+ }
91
+
92
+ // Find the minimum or maximum time interval in a given moment time list
93
+ export function getTimeInterval (times, mode = 'minimum') {
94
+ // Look for the nearest time
95
+ let interval = (mode === 'minimum' ? Infinity : 0)
96
+ times.forEach((currentTime, index) => {
97
+ if (index < (times.length - 1)) {
98
+ const diff = Math.abs(currentTime.diff(times[index + 1]))
99
+ if (mode === 'minimum') {
100
+ if (diff < interval) interval = diff
101
+ } else {
102
+ if (diff > interval) interval = diff
103
+ }
104
+ }
105
+ })
106
+ return interval
107
+ }
108
+
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
+ export function getFeatureId (feature, layer) {
176
+ let featureId = _.get(layer, 'featureId')
177
+ // We need at least an internal ID to uniquely identify features for updates
178
+ if (!featureId) featureId = '_id'
179
+ // Support compound index
180
+ featureId = (Array.isArray(featureId) ? featureId : [featureId])
181
+ return featureId.map(id => _.get(feature, 'properties.' + id, _.get(feature, id))).join('-')
182
+ }
183
+
184
+ export function formatUserCoordinates (lat, lon, format, options) {
185
+ if (format === 'aeronautical') {
186
+ const coords = formatcoords(lat, lon)
187
+ // longitude group: DDMMML where DD is degree (2 digits mandatory)
188
+ // MMM unit is in 0.1 minutes (trailing 0 optional)
189
+ // L is N/S
190
+ const latDeg = coords.latValues.degreesInt.toString().padStart(2, '0')
191
+ const latMin = Math.floor(coords.latValues.secondsTotal / 6).toString().padStart(3, '0')
192
+ const latDir = coords.north ? 'N' : 'S'
193
+ // longitude group: DDDMMML where DDD is degree (3 digits mandatory)
194
+ // MMM unit is in 0.1 minutes (trailing 0 optional)
195
+ // L is W/E
196
+ const lonDeg = coords.lonValues.degreesInt.toString().padStart(3, '0')
197
+ const lonMin = Math.floor(coords.lonValues.secondsTotal / 6).toString().padStart(3, '0')
198
+ const lonDir = coords.east ? 'E' : 'W'
199
+ return `${latDeg}${latMin}${latDir} ${lonDeg}${lonMin}${lonDir}`
200
+ }
201
+ return formatcoords(lat, lon).format(format, options)
202
+ }
203
+
204
+ export function coordinatesToGeoJSON (lat, lon, format, options) {
205
+ return {
206
+ type: 'Feature',
207
+ geometry: {
208
+ type: 'Point',
209
+ coordinates: [lon, lat]
210
+ },
211
+ properties: {
212
+ name: formatcoords(lat, lon).format(format, options)
213
+ }
214
+ }
215
+ }
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
+ }
@@ -1,5 +1,5 @@
1
1
  import { Store, api } from '../../../core.client.js'
2
- import { formatGeocodingResult, parseCoordinates, formatUserCoordinates } from '../utils.js'
2
+ import { formatGeocodingResult, parseCoordinates, formatUserCoordinates } from './utils.js'
3
3
 
4
4
  export async function searchLocation (pattern, options) {
5
5
  const locations = []
@@ -0,0 +1,75 @@
1
+ import _ from 'lodash'
2
+ import { utils as kCoreUtils } from '../../../core/client/index.js'
3
+
4
+ // Get JSON schema from GeoJson feature' properties
5
+ export function generatePropertiesSchema (geoJson, name) {
6
+ const schema = {
7
+ $id: `http://www.kalisio.xyz/schemas/${_.kebabCase(name)}#`,
8
+ title: name,
9
+ $schema: 'http://json-schema.org/draft-07/schema#',
10
+ type: 'object',
11
+ properties: {
12
+ }
13
+ }
14
+ // Enumerate all available properties/values in all features
15
+ const features = (geoJson.type === 'FeatureCollection' ? geoJson.features : [geoJson])
16
+ features.forEach(feature => {
17
+ // FIXME: we don't yet support nested objects in schema
18
+ const properties = (feature.properties ? kCoreUtils.dotify(feature.properties) : {})
19
+ _.forOwn(properties, (value, key) => {
20
+ // Property already registered ?
21
+ if (schema.properties['{key}']) {
22
+ const property = schema.properties[`${key}`]
23
+ // Try to find first non void value to select appropriate type
24
+ if (_.isNil(property)) schema.properties[`${key}`] = value
25
+ } else {
26
+ schema.properties[`${key}`] = value
27
+ }
28
+ })
29
+ })
30
+ _.forOwn(schema.properties, (value, key) => {
31
+ let type = (typeof value)
32
+ // For null/undefined value we will assume string by default
33
+ if ((type === 'object') || (type === 'undefined')) type = 'string'
34
+ schema.properties[`${key}`] = {
35
+ type,
36
+ nullable: true,
37
+ field: {
38
+ component: (type === 'number'
39
+ ? 'form/KNumberField'
40
+ : (type === 'boolean' ? 'form/KToggleField' : 'form/KTextField')),
41
+ helper: key,
42
+ label: key
43
+ }
44
+ }
45
+ })
46
+ return schema
47
+ }
48
+
49
+ export function updatePropertiesSchema (schema) {
50
+ const props = schema.properties
51
+ if (!props) return
52
+
53
+ const bestGuesses = {
54
+ undefined: 'form/KTextField',
55
+ object: 'form/KTextField',
56
+ string: 'form/KTextField',
57
+ number: 'form/KNumberField',
58
+ boolean: 'form/KToggleField'
59
+ }
60
+
61
+ // Loop over declared props and add best guesses to field components based on property type
62
+ for (const prop in props) {
63
+ const propEntry = props[prop]
64
+ // Field already here, skip entry
65
+ if (propEntry.field && propEntry.field.component) continue
66
+
67
+ propEntry.field = {
68
+ component: bestGuesses[propEntry.type],
69
+ label: prop,
70
+ helper: propEntry.description
71
+ }
72
+ }
73
+
74
+ return schema
75
+ }
@@ -0,0 +1,3 @@
1
+ export * from './utils/index.js'
2
+ export * from './leaflet/utils.js'
3
+ export * from './cesium/utils.js'
@@ -0,0 +1,2 @@
1
+ export * from './utils/index.js'
2
+ export * from './cesium/utils.js'
@@ -1,303 +1,4 @@
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
- import chroma from 'chroma-js'
8
- import config from 'config'
9
- import formatcoords from 'formatcoords'
10
- import { buildUrl } from '../../core/common/index.js'
11
- import { api, Store, utils as kCoreUtils } from '../../core/client/index.js'
12
- export * from './leaflet/utils.js'
13
- export * from './cesium/utils.js'
14
-
15
- // Build a color map from a JS object specification
16
- export function buildColorMap (options) {
17
- let colorMap
18
- const classes = _.get(options, 'classes')
19
- const domain = _.get(options, 'domain')
20
- const scale = _.get(options, 'scale')
21
- const invert = _.get(options, 'invertScale')
22
- if (scale) {
23
- if (classes) {
24
- colorMap = chroma.scale(scale).classes(invert ? classes.slice().reverse() : classes)
25
- } else if (domain) {
26
- colorMap = chroma.scale(scale).domain(invert ? domain.slice().reverse() : domain)
27
- }
28
- }
29
- return colorMap
30
- }
31
-
32
- export function transformFeatures (features, transform) {
33
- features.forEach(feature => {
34
- const scaling = _.get(transform, 'scale')
35
- const rotation = _.get(transform, 'rotate')
36
- const translation = _.get(transform, 'translate')
37
- if (scaling) {
38
- scale(feature, scaling.factor,
39
- Object.assign(_.omit(scaling, ['factor']), { mutate: true }))
40
- }
41
- if (rotation) {
42
- rotate(feature, rotation.angle,
43
- Object.assign(_.omit(rotation, ['angle']), { mutate: true }))
44
- }
45
- if (translation) {
46
- // Could be expressed as direction/distance or target point
47
- // Take care that turfjs uses a rhumb line
48
- if (translation.point) {
49
- translation.distance = rhumbDistance(translation.pivot || [0, 0], translation.point)
50
- translation.direction = rhumbBearing(translation.pivot || [0, 0], translation.point)
51
- delete translation.pivot
52
- delete translation.point
53
- }
54
- translate(feature, translation.distance, translation.direction,
55
- Object.assign(_.omit(translation, ['direction', 'distance']), { mutate: true }))
56
- }
57
- })
58
- }
59
-
60
- export async function fetchGeoJson (dataSource, options = {}) {
61
- const response = await fetch(dataSource)
62
- if (response.status !== 200) {
63
- throw new Error(`Impossible to fetch ${dataSource}: ` + response.status)
64
- }
65
- const data = await response.json()
66
- const features = (data.type === 'FeatureCollection' ? data.features : [data])
67
- if (typeof options.processor === 'function') {
68
- features.forEach(feature => options.processor(feature))
69
- } else if (typeof options.processor === 'string') {
70
- const compiler = _.template(options.processor)
71
- features.forEach(feature => compiler({ feature, properties: feature.properties }))
72
- }
73
- if (options.transform) {
74
- transformFeatures(features, options.transform)
75
- }
76
- return data
77
- }
78
-
79
- // Find the nearest time of a given one in a given moment time list
80
- export function getNearestTime (time, times) {
81
- // Look for the nearest time
82
- let timeIndex = -1
83
- let minDiff = Infinity
84
- times.forEach((currentTime, index) => {
85
- const diff = Math.abs(time.diff(currentTime))
86
- if (diff < minDiff) {
87
- minDiff = diff
88
- timeIndex = index
89
- }
90
- })
91
- return { index: timeIndex, difference: minDiff }
92
- }
93
-
94
- // Find the minimum or maximum time interval in a given moment time list
95
- export function getTimeInterval (times, mode = 'minimum') {
96
- // Look for the nearest time
97
- let interval = (mode === 'minimum' ? Infinity : 0)
98
- times.forEach((currentTime, index) => {
99
- if (index < (times.length - 1)) {
100
- const diff = Math.abs(currentTime.diff(times[index + 1]))
101
- if (mode === 'minimum') {
102
- if (diff < interval) interval = diff
103
- } else {
104
- if (diff > interval) interval = diff
105
- }
106
- }
107
- })
108
- return interval
109
- }
110
-
111
- // Format (reverse) geocoding output
112
- export function formatGeocodingResult (element) {
113
- let label = element.formattedAddress || ''
114
- if (!label) {
115
- if (element.streetNumber) label += (element.streetNumber + ', ')
116
- if (element.streetName) label += (element.streetName + ' ')
117
- if (element.city) label += (element.city + ' ')
118
- if (element.zipcode) label += (' (' + element.zipcode + ')')
119
- }
120
- return label
121
- }
122
-
123
- // Helper to set a JWT as query param in a target URL
124
- export function setUrlJwt (item, path, baseUrl, jwtField, jwt) {
125
- const url = _.get(item, path)
126
- if (!url) return
127
- // Check it conforms to required base URL
128
- if (!url.startsWith(baseUrl)) return
129
- // FIXME: specific case of Cesium OpenStreetMap provider
130
- // Because Cesium generates the final url as base url + tile scheme + extension
131
- // updating the base url property breaks it, for now we modify the extension
132
- if ((path === 'cesium.url') && _.get(item, 'cesium.type') === 'OpenStreetMap') {
133
- const ext = _.get(item, 'cesium.fileExtension', 'png')
134
- _.set(item, 'cesium.fileExtension', ext + `?${jwtField}=${jwt}`)
135
- } else {
136
- _.set(item, path, buildUrl(url, { [jwtField]: jwt }))
137
- }
138
- }
139
-
140
- // Helper to set required JWT as query param in a given set of layers for underlying engine
141
- export async function setEngineJwt (layers) {
142
- // If we need to use API gateway forward token as query parameter
143
- // (Leaflet does not support anything else by default as it mainly uses raw <img> tags)
144
- let jwt = (config.gatewayJwt ? await api.get('storage').getItem(config.gatewayJwt) : null)
145
- let jwtField = config.gatewayJwtField
146
- // Check both the default built-in config or the server provided one if any (eg mobile apps)
147
- const gatewayUrl = Store.get('capabilities.api.gateway', config.gateway)
148
- if (jwt) {
149
- layers.forEach(layer => {
150
- setUrlJwt(layer, 'iconUrl', gatewayUrl, jwtField, jwt)
151
- setUrlJwt(layer, 'leaflet.source', gatewayUrl, jwtField, jwt)
152
- setUrlJwt(layer, 'opendap.url', gatewayUrl, jwtField, jwt)
153
- setUrlJwt(layer, 'geotiff.url', gatewayUrl, jwtField, jwt)
154
- setUrlJwt(layer, 'wfs.url', gatewayUrl, jwtField, jwt)
155
- setUrlJwt(layer, 'wcs.url', gatewayUrl, jwtField, jwt)
156
- setUrlJwt(layer, 'cesium.url', gatewayUrl, jwtField, jwt)
157
- setUrlJwt(layer, 'cesium.source', gatewayUrl, jwtField, jwt)
158
- })
159
- }
160
- // We might also proxy some data directly from the app when using object storage
161
- // This is only for raw raster data not OGC protocols
162
- jwt = (config.apiJwt ? await api.get('storage').getItem(config.apiJwt) : null)
163
- jwtField = 'jwt'
164
- const apiUrl = api.getBaseUrl()
165
- if (jwt) {
166
- layers.forEach(layer => {
167
- setUrlJwt(layer, 'geotiff.url', apiUrl, jwtField, jwt)
168
- })
169
- // We also allow absolute URLs for app like /api/storage/xxx
170
- layers.forEach(layer => {
171
- setUrlJwt(layer, 'geotiff.url', '/', jwtField, jwt)
172
- })
173
- }
174
- return layers
175
- }
176
-
177
- export function getFeatureId (feature, layer) {
178
- let featureId = _.get(layer, 'featureId')
179
- // We need at least an internal ID to uniquely identify features for updates
180
- if (!featureId) featureId = '_id'
181
- // Support compound index
182
- featureId = (Array.isArray(featureId) ? featureId : [featureId])
183
- return featureId.map(id => _.get(feature, 'properties.' + id, _.get(feature, id))).join('-')
184
- }
185
-
186
- // Get JSON schema from GeoJson feature' properties
187
- export function generatePropertiesSchema (geoJson, name) {
188
- const schema = {
189
- $id: `http://www.kalisio.xyz/schemas/${_.kebabCase(name)}#`,
190
- title: name,
191
- $schema: 'http://json-schema.org/draft-07/schema#',
192
- type: 'object',
193
- properties: {
194
- }
195
- }
196
- // Enumerate all available properties/values in all features
197
- const features = (geoJson.type === 'FeatureCollection' ? geoJson.features : [geoJson])
198
- features.forEach(feature => {
199
- // FIXME: we don't yet support nested objects in schema
200
- const properties = (feature.properties ? kCoreUtils.dotify(feature.properties) : {})
201
- _.forOwn(properties, (value, key) => {
202
- // Property already registered ?
203
- if (schema.properties['{key}']) {
204
- const property = schema.properties[`${key}`]
205
- // Try to find first non void value to select appropriate type
206
- if (_.isNil(property)) schema.properties[`${key}`] = value
207
- } else {
208
- schema.properties[`${key}`] = value
209
- }
210
- })
211
- })
212
- _.forOwn(schema.properties, (value, key) => {
213
- let type = (typeof value)
214
- // For null/undefined value we will assume string by default
215
- if ((type === 'object') || (type === 'undefined')) type = 'string'
216
- schema.properties[`${key}`] = {
217
- type,
218
- nullable: true,
219
- field: {
220
- component: (type === 'number'
221
- ? 'form/KNumberField'
222
- : (type === 'boolean' ? 'form/KToggleField' : 'form/KTextField')),
223
- helper: key,
224
- label: key
225
- }
226
- }
227
- })
228
- return schema
229
- }
230
-
231
- export function updatePropertiesSchema (schema) {
232
- const props = schema.properties
233
- if (!props) return
234
-
235
- const bestGuesses = {
236
- undefined: 'form/KTextField',
237
- object: 'form/KTextField',
238
- string: 'form/KTextField',
239
- number: 'form/KNumberField',
240
- boolean: 'form/KToggleField'
241
- }
242
-
243
- // Loop over declared props and add best guesses to field components based on property type
244
- for (const prop in props) {
245
- const propEntry = props[prop]
246
- // Field already here, skip entry
247
- if (propEntry.field && propEntry.field.component) continue
248
-
249
- propEntry.field = {
250
- component: bestGuesses[propEntry.type],
251
- label: prop,
252
- helper: propEntry.description
253
- }
254
- }
255
-
256
- return schema
257
- }
258
-
259
- export function formatUserCoordinates (lat, lon, format, options) {
260
- if (format === 'aeronautical') {
261
- const coords = formatcoords(lat, lon)
262
- // longitude group: DDMMML where DD is degree (2 digits mandatory)
263
- // MMM unit is in 0.1 minutes (trailing 0 optional)
264
- // L is N/S
265
- const latDeg = coords.latValues.degreesInt.toString().padStart(2, '0')
266
- const latMin = Math.floor(coords.latValues.secondsTotal / 6).toString().padStart(3, '0')
267
- const latDir = coords.north ? 'N' : 'S'
268
- // longitude group: DDDMMML where DDD is degree (3 digits mandatory)
269
- // MMM unit is in 0.1 minutes (trailing 0 optional)
270
- // L is W/E
271
- const lonDeg = coords.lonValues.degreesInt.toString().padStart(3, '0')
272
- const lonMin = Math.floor(coords.lonValues.secondsTotal / 6).toString().padStart(3, '0')
273
- const lonDir = coords.east ? 'E' : 'W'
274
- return `${latDeg}${latMin}${latDir} ${lonDeg}${lonMin}${lonDir}`
275
- }
276
- return formatcoords(lat, lon).format(format, options)
277
- }
278
-
279
- export function coordinatesToGeoJSON (lat, lon, format, options) {
280
- return {
281
- type: 'Feature',
282
- geometry: {
283
- type: 'Point',
284
- coordinates: [lon, lat]
285
- },
286
- properties: {
287
- name: formatcoords(lat, lon).format(format, options)
288
- }
289
- }
290
- }
291
-
292
- export function parseCoordinates (str) {
293
- const coords = _.split(_.trim(str), ',')
294
- if (coords.length !== 2) return
295
- const latitude = Number(coords[0])
296
- if (_.isNaN(latitude)) return
297
- const longitude = Number(coords[1])
298
- if (_.isNaN(longitude)) return
299
- return {
300
- latitude,
301
- longitude
302
- }
303
- }
1
+ export * from './utils/index.js'
2
+ // Now in separated files to avoid mixin Leaflet/Cesium elements
3
+ // export * from './leaflet/utils.js'
4
+ // export * from './cesium/utils.js'
@@ -0,0 +1,2 @@
1
+ export * from './utils/index.js'
2
+ export * from './leaflet/utils.js'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kalisio/kdk",
3
3
  "description": "Kalisio Development Kit",
4
- "version": "2.1.5",
4
+ "version": "2.1.6",
5
5
  "homepage": "https://github.com/kalisio/kdk",
6
6
  "type": "module",
7
7
  "keywords": [
@@ -52,6 +52,24 @@ export async function clickLayer (page, tabId, layer, wait = 1000) {
52
52
  await page.waitForTimeout(wait)
53
53
  }
54
54
 
55
+ export async function zoomToLayer (page, tabId, layer, wait = 1000) {
56
+ const isCatalogOpened = await clickCatalogTab(page, tabId)
57
+ const layerId = getLayerId(layer)
58
+ const categoryId = await getLayerCategoryId(page, layerId)
59
+ let isCategoryOpened
60
+ if (categoryId) {
61
+ isCategoryOpened = await isLayerCategoryOpened(page, categoryId)
62
+ if (!isCategoryOpened) await core.clickPaneAction(page, 'right', categoryId, 1000)
63
+ }
64
+ await core.click(page, `#${layer}-actions`)
65
+ await core.clickAction(page, 'zoom-to-layer')
66
+ if (categoryId) {
67
+ if (!isCategoryOpened) await core.clickPaneAction(page, 'right', categoryId, 500)
68
+ }
69
+ if (!isCatalogOpened) await core.clickOpener(page, 'right')
70
+ await page.waitForTimeout(wait)
71
+ }
72
+
55
73
  export async function saveLayer (page, tabId, layer, wait = 1000) {
56
74
  const isCatalogOpened = await clickCatalogTab(page, tabId)
57
75
  const layerId = getLayerId(layer)