@kalisio/kdk 1.6.0 → 1.7.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 +1 -1
- package/CHANGELOG.md +63 -13
- package/extras/tours/map/catalog-panel.js +61 -10
- package/extras/tours/map/create-view.js +25 -0
- package/extras/tours/map/fab.js +10 -1
- package/extras/tours/map/navigation-bar.js +7 -9
- package/lib/core/api/application.js +9 -1
- package/lib/core/api/application.js.map +1 -1
- package/lib/core/client/components/chart/KChart.vue +115 -0
- package/lib/core/client/components/chart/KStatsChart.vue +76 -0
- package/lib/core/client/components/chart/index.js +20 -0
- package/lib/core/client/components/chart/index.js.map +1 -0
- package/lib/core/client/components/collection/KFilter.vue +6 -0
- package/lib/core/client/components/collection/KHistoryEntry.vue +1 -1
- package/lib/core/client/components/collection/KItem.vue +4 -4
- package/lib/core/client/components/form/KChipsField.vue +4 -4
- package/lib/core/client/components/form/KView.vue +1 -1
- package/lib/core/client/components/frame/KAction.vue +40 -5
- package/lib/core/client/components/frame/KBlock.vue +7 -7
- package/lib/core/client/components/frame/KContent.vue +1 -1
- package/lib/core/client/components/frame/KOpener.vue +1 -1
- package/lib/core/client/components/frame/KPanel.vue +8 -7
- package/lib/core/client/components/frame/KPopupAction.vue +6 -0
- package/lib/core/client/components/frame/KScreen.vue +1 -1
- package/lib/core/client/components/frame/KStamp.vue +4 -2
- package/lib/core/client/components/frame/index.js +1 -6
- package/lib/core/client/components/frame/index.js.map +1 -1
- package/lib/core/client/components/input/KIconChooser.vue +5 -2
- package/lib/core/client/components/input/KPalette.vue +1 -1
- package/lib/core/client/components/input/index.js +14 -4
- package/lib/core/client/components/input/index.js.map +1 -1
- package/lib/core/client/components/layout/KFab.vue +1 -1
- package/lib/core/client/components/layout/KLayout.vue +10 -2
- package/lib/core/client/components/layout/KPage.vue +19 -18
- package/lib/core/client/components/layout/KTour.vue +5 -3
- package/lib/core/client/components/layout/KWindow.vue +211 -59
- package/lib/core/client/components/media/KMediaBrowser.vue +6 -6
- package/lib/core/client/components/menu/KMenu.vue +13 -5
- package/lib/core/client/components/menu/KRadialFab.vue +22 -24
- package/lib/core/client/components/menu/KRadialFabItem.vue +17 -18
- package/lib/core/client/components/team/KGroupsActivity.vue +1 -1
- package/lib/core/client/components/team/KJoinGroup.vue +2 -2
- package/lib/core/client/components/time/KAbsoluteTimeRange.vue +190 -0
- package/lib/core/client/components/time/KRelativeTimeRanges.vue +192 -0
- package/lib/core/client/components/time/index.js +20 -0
- package/lib/core/client/components/time/index.js.map +1 -0
- package/lib/core/client/i18n/core_en.json +35 -12
- package/lib/core/client/i18n/core_fr.json +36 -13
- package/lib/core/client/index.js +1 -1
- package/lib/core/client/index.js.map +1 -1
- package/lib/core/client/mixins/mixin.base-collection.js +11 -1
- package/lib/core/client/mixins/mixin.base-collection.js.map +1 -1
- package/lib/core/client/mixins/mixin.base-widget.js +25 -13
- package/lib/core/client/mixins/mixin.base-widget.js.map +1 -1
- package/lib/core/client/services/index.js +2 -1
- package/lib/core/client/services/index.js.map +1 -1
- package/lib/core/client/services/local-settings.service.js +4 -0
- package/lib/core/client/services/local-settings.service.js.map +1 -1
- package/lib/core/client/time.js +25 -15
- package/lib/core/client/time.js.map +1 -1
- package/lib/core/client/units.js +12 -0
- package/lib/core/client/units.js.map +1 -1
- package/lib/core/client/utils.js +11 -0
- package/lib/core/client/utils.js.map +1 -1
- package/lib/core/common/schemas/settings.update.json +14 -4
- package/lib/map/api/hooks/hooks.catalog.js +20 -0
- package/lib/map/api/hooks/hooks.catalog.js.map +1 -1
- package/lib/map/api/models/catalog.model.mongodb.js +6 -1
- package/lib/map/api/models/catalog.model.mongodb.js.map +1 -1
- package/lib/map/api/models/features.model.mongodb.js +5 -0
- package/lib/map/api/models/features.model.mongodb.js.map +1 -1
- package/lib/map/api/services/catalog/catalog.hooks.js +3 -1
- package/lib/map/api/services/catalog/catalog.hooks.js.map +1 -1
- package/lib/map/client/components/KColorLegend.vue +22 -21
- package/lib/map/client/components/KFeaturesChart.vue +81 -110
- package/lib/map/client/components/KLayerStyleForm.vue +119 -47
- package/lib/map/client/components/KLevelSlider.vue +30 -29
- package/lib/map/client/components/KLocationMap.vue +2 -2
- package/lib/map/client/components/KMeasureTool.vue +30 -6
- package/lib/map/client/components/KPositionIndicator.vue +4 -4
- package/lib/map/client/components/KTimeline.vue +25 -27
- package/lib/map/client/components/KTimezoneMap.vue +156 -0
- package/lib/map/client/components/KUrlLegend.vue +11 -10
- package/lib/map/client/components/catalog/KBaseLayersSelector.vue +1 -1
- package/lib/map/client/components/catalog/KCatalogLayersPanel.vue +56 -0
- package/lib/map/client/components/catalog/KCreateView.vue +91 -0
- package/lib/map/client/components/catalog/KLayerCategories.vue +2 -1
- package/lib/map/client/components/catalog/{KCatalog.vue → KLayersPanel.vue} +19 -37
- package/lib/map/client/components/catalog/KUserLayersPanel.vue +40 -0
- package/lib/map/client/components/catalog/KViewSelector.vue +46 -0
- package/lib/map/client/components/catalog/KViewsPanel.vue +110 -0
- package/lib/map/client/components/catalog/KWeatherLayersSelector.vue +4 -13
- package/lib/map/client/components/form/KTimezoneField.vue +135 -0
- package/lib/map/client/components/widget/KElevationProfile.vue +488 -0
- package/lib/map/client/components/widget/KInformationBox.vue +48 -23
- package/lib/map/client/components/widget/KMapillaryViewer.vue +26 -20
- package/lib/map/client/components/widget/KTimeSeries.vue +267 -347
- package/lib/map/client/i18n/map_en.json +63 -40
- package/lib/map/client/i18n/map_fr.json +65 -42
- package/lib/map/client/leaflet/GradientPath.js +40 -19
- package/lib/map/client/leaflet/GradientPath.js.map +1 -1
- package/lib/map/client/leaflet/TiledFeatureLayer.js +527 -93
- package/lib/map/client/leaflet/TiledFeatureLayer.js.map +1 -1
- package/lib/map/client/leaflet/TiledMeshLayer.js +58 -35
- package/lib/map/client/leaflet/TiledMeshLayer.js.map +1 -1
- package/lib/map/client/leaflet/utils.js +44 -3
- package/lib/map/client/leaflet/utils.js.map +1 -1
- package/lib/map/client/mixins/globe/mixin.base-globe.js +16 -1
- package/lib/map/client/mixins/globe/mixin.base-globe.js.map +1 -1
- package/lib/map/client/mixins/globe/mixin.file-layers.js +12 -2
- package/lib/map/client/mixins/globe/mixin.file-layers.js.map +1 -1
- package/lib/map/client/mixins/globe/mixin.geojson-layers.js +7 -6
- package/lib/map/client/mixins/globe/mixin.geojson-layers.js.map +1 -1
- package/lib/map/client/mixins/globe/mixin.popup.js +4 -2
- package/lib/map/client/mixins/globe/mixin.popup.js.map +1 -1
- package/lib/map/client/mixins/globe/mixin.style.js +8 -4
- package/lib/map/client/mixins/globe/mixin.style.js.map +1 -1
- package/lib/map/client/mixins/globe/mixin.tooltip.js +4 -2
- package/lib/map/client/mixins/globe/mixin.tooltip.js.map +1 -1
- package/lib/map/client/mixins/index.js +23 -18
- package/lib/map/client/mixins/index.js.map +1 -1
- package/lib/map/client/mixins/map/mixin.base-map.js +20 -2
- package/lib/map/client/mixins/map/mixin.base-map.js.map +1 -1
- package/lib/map/client/mixins/map/mixin.edit-layers.js +8 -4
- package/lib/map/client/mixins/map/mixin.edit-layers.js.map +1 -1
- package/lib/map/client/mixins/map/mixin.geojson-layers.js +27 -5
- package/lib/map/client/mixins/map/mixin.geojson-layers.js.map +1 -1
- package/lib/map/client/mixins/map/mixin.heatmap-layers.js +6 -1
- package/lib/map/client/mixins/map/mixin.heatmap-layers.js.map +1 -1
- package/lib/map/client/mixins/map/mixin.popup.js +1 -1
- package/lib/map/client/mixins/map/mixin.popup.js.map +1 -1
- package/lib/map/client/mixins/map/mixin.style.js +8 -4
- package/lib/map/client/mixins/map/mixin.style.js.map +1 -1
- package/lib/map/client/mixins/map/mixin.tiled-mesh-layers.js +4 -11
- package/lib/map/client/mixins/map/mixin.tiled-mesh-layers.js.map +1 -1
- package/lib/map/client/mixins/map/mixin.tiled-wind-layers.js +0 -11
- package/lib/map/client/mixins/map/mixin.tiled-wind-layers.js.map +1 -1
- package/lib/map/client/mixins/map/mixin.tooltip.js +1 -1
- package/lib/map/client/mixins/map/mixin.tooltip.js.map +1 -1
- package/lib/map/client/mixins/mixin.activity.js +150 -150
- package/lib/map/client/mixins/mixin.activity.js.map +1 -1
- package/lib/map/client/mixins/mixin.catalog-panel.js +36 -0
- package/lib/map/client/mixins/mixin.catalog-panel.js.map +1 -0
- package/lib/map/client/mixins/mixin.feature-selection.js +17 -8
- package/lib/map/client/mixins/mixin.feature-selection.js.map +1 -1
- package/lib/map/client/mixins/mixin.feature-service.js +36 -16
- package/lib/map/client/mixins/mixin.feature-service.js.map +1 -1
- package/lib/map/client/mixins/mixin.infobox.js +1 -1
- package/lib/map/client/mixins/mixin.infobox.js.map +1 -1
- package/lib/map/client/mixins/mixin.levels.js +26 -12
- package/lib/map/client/mixins/mixin.levels.js.map +1 -1
- package/lib/map/client/mixins/mixin.style.js +1 -0
- package/lib/map/client/mixins/mixin.style.js.map +1 -1
- package/lib/map/client/mixins/mixin.weacast.js +15 -23
- package/lib/map/client/mixins/mixin.weacast.js.map +1 -1
- package/lib/map/client/pixi-utils.js +8 -177
- package/lib/map/client/pixi-utils.js.map +1 -1
- package/lib/map/client/utils.js +1 -0
- package/lib/map/client/utils.js.map +1 -1
- package/lib/map/common/grid.js +131 -0
- package/lib/map/common/grid.js.map +1 -1
- package/lib/map/common/index.js +22 -11
- package/lib/map/common/index.js.map +1 -1
- package/lib/map/common/meteo-model-grid-source.js +5 -2
- package/lib/map/common/meteo-model-grid-source.js.map +1 -1
- package/lib/map/common/time-based-grid-source.js +5 -2
- package/lib/map/common/time-based-grid-source.js.map +1 -1
- package/lib/test/client/core/collection.js +31 -13
- package/lib/test/client/core/collection.js.map +1 -1
- package/lib/test/client/core/layout.js +137 -49
- package/lib/test/client/core/layout.js.map +1 -1
- package/lib/test/client/core/utils.js +89 -22
- package/lib/test/client/core/utils.js.map +1 -1
- package/lib/test/client/map/catalog.js +134 -41
- package/lib/test/client/map/catalog.js.map +1 -1
- package/lib/test/client/map/controls.js +7 -4
- package/lib/test/client/map/controls.js.map +1 -1
- package/lib/test/client/map/index.js +12 -0
- package/lib/test/client/map/index.js.map +1 -1
- package/lib/test/client/map/utils.js +67 -0
- package/lib/test/client/map/utils.js.map +1 -0
- package/package.json +5 -5
- package/extras/tours/map/favorite-views.js +0 -53
- package/lib/core/client/components/frame/KChart.vue +0 -60
- package/lib/core/client/components/input/KCodeInput.vue +0 -50
- package/lib/core/client/components/input/KTimeRangeChooser.vue +0 -109
- package/lib/core/client/components/time/KTimeRange.vue +0 -144
- package/lib/map/client/components/KFavoriteViews.vue +0 -217
|
@@ -25,16 +25,10 @@
|
|
|
25
25
|
</q-select>
|
|
26
26
|
</div>
|
|
27
27
|
</template>
|
|
28
|
-
<template v-if="hasArchiveLayers" v-slot:footer>
|
|
29
|
-
<q-tabs class="q-ma-sm text-primary" no-caps v-model="mode" @input="onModeChanged">
|
|
30
|
-
<q-tab id="forecast" name="forecast" :label="$t('KWeatherLayersSelector.FORECASTS_LABEL')" />
|
|
31
|
-
<q-tab id="archive" name="archive" :label="$t('KWeatherLayersSelector.ARCHIVES_LABEL')" />
|
|
32
|
-
</q-tabs>
|
|
33
|
-
</template>
|
|
34
28
|
</k-layers-selector>
|
|
35
29
|
</div>
|
|
36
30
|
<div v-else class="row justify-center q-pa-sm">
|
|
37
|
-
<k-stamp icon="las la-exclamation-circle" icon-size="sm" :text="$t('KWeatherLayersSelector.NO_MODEL_AVAILABLE')" direction="horizontal" />
|
|
31
|
+
<k-stamp icon="las la-exclamation-circle" icon-size="sm" :text="$t('KWeatherLayersSelector.NO_MODEL_AVAILABLE')" text-size="0.9rem" direction="horizontal" />
|
|
38
32
|
</div>
|
|
39
33
|
</template>
|
|
40
34
|
|
|
@@ -66,9 +60,6 @@ export default {
|
|
|
66
60
|
}
|
|
67
61
|
},
|
|
68
62
|
computed: {
|
|
69
|
-
hasArchiveLayers () {
|
|
70
|
-
return _.find(this.layers, (layer) => { return layer.tags.includes('archive') })
|
|
71
|
-
},
|
|
72
63
|
filteredLayers () {
|
|
73
64
|
if (this.mode === 'forecast') return this.filterForecastLayers()
|
|
74
65
|
return this.filterArchiveLayers()
|
|
@@ -121,14 +112,14 @@ export default {
|
|
|
121
112
|
},
|
|
122
113
|
onModelChanged (model) {
|
|
123
114
|
this.callHandler('toggle', model)
|
|
124
|
-
},
|
|
125
|
-
onModeChanged (mode) {
|
|
126
115
|
}
|
|
127
116
|
},
|
|
128
|
-
|
|
117
|
+
beforeCreate () {
|
|
129
118
|
// Loads the required components
|
|
130
119
|
this.$options.components['k-layers-selector'] = this.$load('catalog/KLayersSelector')
|
|
131
120
|
this.$options.components['k-stamp'] = this.$load('frame/KStamp')
|
|
121
|
+
},
|
|
122
|
+
created () {
|
|
132
123
|
// Set the current forecast model
|
|
133
124
|
this.model = this.forecastModel
|
|
134
125
|
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<div v-if="readOnly" :id="properties.name + '-field'">
|
|
4
|
+
{{ model }}
|
|
5
|
+
</div>
|
|
6
|
+
<div v-else>
|
|
7
|
+
<q-select
|
|
8
|
+
:id="properties.name + '-field'"
|
|
9
|
+
v-model="model"
|
|
10
|
+
:label="label"
|
|
11
|
+
:options="options"
|
|
12
|
+
use-input
|
|
13
|
+
@input='onChanged'
|
|
14
|
+
@filter="onAutocomplete"
|
|
15
|
+
emit-value
|
|
16
|
+
map-options
|
|
17
|
+
:error="hasError"
|
|
18
|
+
:error-message="errorLabel"
|
|
19
|
+
:disabled="disabled"
|
|
20
|
+
bottom-slots>
|
|
21
|
+
<!-- Map display -->
|
|
22
|
+
<template v-slot:prepend>
|
|
23
|
+
<k-action
|
|
24
|
+
id="timezone-map"
|
|
25
|
+
icon="las la-map-marker"
|
|
26
|
+
color="primary"
|
|
27
|
+
:handler="openTimezoneMap"
|
|
28
|
+
:tooltip="$t('KTimezoneField.TIMEZONE_MAP_TOOLTIP')" />
|
|
29
|
+
</template>
|
|
30
|
+
<!-- Options display -->
|
|
31
|
+
<template v-slot:option="scope">
|
|
32
|
+
<q-item
|
|
33
|
+
:id="scope.opt.value"
|
|
34
|
+
v-bind="scope.itemProps"
|
|
35
|
+
v-on="scope.itemEvents"
|
|
36
|
+
>
|
|
37
|
+
<q-item-section>
|
|
38
|
+
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
|
39
|
+
</q-item-section>
|
|
40
|
+
</q-item>
|
|
41
|
+
</template>
|
|
42
|
+
<template v-slot:append>
|
|
43
|
+
<q-icon
|
|
44
|
+
v-if="model"
|
|
45
|
+
class="cursor-pointer"
|
|
46
|
+
name="cancel"
|
|
47
|
+
@click.stop="fill('')"
|
|
48
|
+
/>
|
|
49
|
+
</template>
|
|
50
|
+
<!-- Helper -->
|
|
51
|
+
<template v-if="helper" v-slot:hint>
|
|
52
|
+
<span v-html="helper"></span>
|
|
53
|
+
</template>
|
|
54
|
+
</q-select>
|
|
55
|
+
<k-modal ref="timezoneMapModal"
|
|
56
|
+
:title="$t('KTimezoneField.TIMEZONE_MAP_TITLE')"
|
|
57
|
+
:buttons="getTimezoneMapModalButtons()"
|
|
58
|
+
:options="{}">
|
|
59
|
+
<k-timezone-map id="timezones-map" style="min-height: 250px;" :value="this.model" @timezone-selected="onMapTimezoneSelected"/>
|
|
60
|
+
</k-modal>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</template>
|
|
64
|
+
|
|
65
|
+
<script>
|
|
66
|
+
import _ from 'lodash'
|
|
67
|
+
import moment from 'moment-timezone/builds/moment-timezone-with-data-10-year-range'
|
|
68
|
+
import { mixins as kCoreMixins, utils as kCoreUtils } from '../../../../core/client'
|
|
69
|
+
import meta from 'moment-timezone/data/meta/latest.json'
|
|
70
|
+
|
|
71
|
+
const timezones = moment.tz.names()
|
|
72
|
+
// Timezone names contains additional "usual" timezone namings like GMT+1, etc.
|
|
73
|
+
// const timezones = _.keys(meta.zones)
|
|
74
|
+
const countries = _.values(meta.countries)
|
|
75
|
+
|
|
76
|
+
export default {
|
|
77
|
+
name: 'k-timezone-field',
|
|
78
|
+
mixins: [kCoreMixins.baseField],
|
|
79
|
+
data () {
|
|
80
|
+
return {
|
|
81
|
+
options: timezones.map(timezone => ({ value: timezone, label: kCoreUtils.getTimezoneLabel(timezone) }))
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
methods: {
|
|
85
|
+
getTimezoneMapModalButtons () {
|
|
86
|
+
return [
|
|
87
|
+
{ id: 'cancel-button', label: 'CANCEL', renderer: 'form-button', outline: true, handler: () => this.closeTimezoneMap() },
|
|
88
|
+
{ id: 'apply-button', label: 'APPLY', renderer: 'form-button', handler: () => this.closeTimezoneMap(true) }
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
onMapTimezoneSelected (timezone) {
|
|
92
|
+
this.mapTimezone = timezone
|
|
93
|
+
},
|
|
94
|
+
openTimezoneMap () {
|
|
95
|
+
this.$refs.timezoneMapModal.open()
|
|
96
|
+
},
|
|
97
|
+
async closeTimezoneMap (fill = false) {
|
|
98
|
+
this.$refs.timezoneMapModal.close()
|
|
99
|
+
if (fill) {
|
|
100
|
+
this.fill(this.mapTimezone)
|
|
101
|
+
// Seems to be required to correctly update the label in the q-select
|
|
102
|
+
await this.$nextTick()
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
onAutocomplete (value, update) {
|
|
106
|
+
// Check for any matching country also
|
|
107
|
+
const matchingCountries = countries.filter(country => country.name.toLocaleLowerCase().includes(value.toLocaleLowerCase()))
|
|
108
|
+
update(() => {
|
|
109
|
+
this.options = timezones
|
|
110
|
+
.filter(timezone => {
|
|
111
|
+
// Filter applies to timezone names or country names
|
|
112
|
+
const matchTimezone = timezone.toLocaleLowerCase().includes(value.toLocaleLowerCase())
|
|
113
|
+
if (matchTimezone) return true
|
|
114
|
+
// We have the list of timezones associated to each matching country
|
|
115
|
+
for (let i = 0; i < matchingCountries.length; i++) {
|
|
116
|
+
const matchingTimezones = matchingCountries[i].zones
|
|
117
|
+
if (matchingTimezones.includes(timezone)) return true
|
|
118
|
+
}
|
|
119
|
+
return false
|
|
120
|
+
})
|
|
121
|
+
.map(timezone => ({ value: timezone, label: kCoreUtils.getTimezoneLabel(timezone) }))
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
created () {
|
|
126
|
+
// Load the required components
|
|
127
|
+
this.$options.components['k-action'] = this.$load('frame/KAction')
|
|
128
|
+
this.$options.components['k-modal'] = this.$load('frame/KModal')
|
|
129
|
+
this.$options.components['k-timezone-map'] = this.$load('KTimezoneMap')
|
|
130
|
+
|
|
131
|
+
// Load metadata
|
|
132
|
+
this.meta = require('moment-timezone/data/meta/latest.json')
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
</script>
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div id="elevation-profile" class="column" :style="widgetStyle">
|
|
3
|
+
<k-chart ref="chart" class="col q-pl-sm q-pr-sm" />
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script>
|
|
8
|
+
import _ from 'lodash'
|
|
9
|
+
import logger from 'loglevel'
|
|
10
|
+
import { baseWidget } from '../../../../core/client/mixins'
|
|
11
|
+
import { Units } from '../../../../core/client/units'
|
|
12
|
+
import { colors, copyToClipboard, exportFile } from 'quasar'
|
|
13
|
+
import along from '@turf/along'
|
|
14
|
+
import length from '@turf/length'
|
|
15
|
+
import flatten from '@turf/flatten'
|
|
16
|
+
import { segmentEach, coordEach } from '@turf/meta'
|
|
17
|
+
import { featureCollection } from '@turf/helpers'
|
|
18
|
+
|
|
19
|
+
export default {
|
|
20
|
+
name: 'k-elevation-profile',
|
|
21
|
+
inject: ['kActivity'],
|
|
22
|
+
mixins: [baseWidget],
|
|
23
|
+
props: {
|
|
24
|
+
feature: {
|
|
25
|
+
type: Object,
|
|
26
|
+
default: null
|
|
27
|
+
},
|
|
28
|
+
layer: {
|
|
29
|
+
type: Object,
|
|
30
|
+
default: null
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
computed: {
|
|
34
|
+
title () {
|
|
35
|
+
return _.get(this.feature, 'name') ||
|
|
36
|
+
_.get(this.feature, 'label') ||
|
|
37
|
+
_.get(this.feature, 'properties.name') ||
|
|
38
|
+
_.get(this.feature, 'properties.label') ||
|
|
39
|
+
_.get(this.layer, 'name') ||
|
|
40
|
+
_.get(this.layer, 'properties.name')
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
data () {
|
|
44
|
+
return {
|
|
45
|
+
profile: null
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
watch: {
|
|
49
|
+
feature: {
|
|
50
|
+
immediate: true,
|
|
51
|
+
handler () {
|
|
52
|
+
this.refresh()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
methods: {
|
|
57
|
+
refreshActions () {
|
|
58
|
+
this.$store.patch('window', {
|
|
59
|
+
widgetActions: [
|
|
60
|
+
{
|
|
61
|
+
id: 'center-view',
|
|
62
|
+
icon: 'las la-eye',
|
|
63
|
+
tooltip: this.$t('KElevationProfile.CENTER_ON'),
|
|
64
|
+
visible: this.feature,
|
|
65
|
+
handler: this.onCenterOn
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: 'copy-properties',
|
|
69
|
+
icon: 'las la-clipboard',
|
|
70
|
+
tooltip: this.$t('KElevationProfile.COPY_PROFILE'),
|
|
71
|
+
visible: this.profile,
|
|
72
|
+
handler: this.onCopyProfile
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'export-feature',
|
|
76
|
+
icon: 'img:statics/json-icon.svg',
|
|
77
|
+
tooltip: this.$t('KElevationProfile.EXPORT_PROFILE'),
|
|
78
|
+
visible: this.profile,
|
|
79
|
+
handler: this.onExportProfile
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
})
|
|
83
|
+
},
|
|
84
|
+
extractProfileData (profiles) {
|
|
85
|
+
// Extract profile heights if available on the segments used to compute elevation
|
|
86
|
+
const profileHeights = []
|
|
87
|
+
const profileLabels = []
|
|
88
|
+
let allCoordsHaveHeight = true
|
|
89
|
+
let curvilinearOffset = 0
|
|
90
|
+
for (let i = 0; i < profiles.length && allCoordsHaveHeight; ++i) {
|
|
91
|
+
const dataUnit = _.get(profiles[i], 'properties.altitudeUnit', 'm')
|
|
92
|
+
// Gather elevation at each coord, make sure all coords have height along the way
|
|
93
|
+
coordEach(profiles[i], (coord) => {
|
|
94
|
+
if (coord.length > 2) profileHeights.push(Units.convert(coord[2], dataUnit, this.chartHeightUnit))
|
|
95
|
+
else allCoordsHaveHeight = false
|
|
96
|
+
})
|
|
97
|
+
// Compute curvilinear abscissa at each point
|
|
98
|
+
if (allCoordsHaveHeight) {
|
|
99
|
+
segmentEach(profiles[i], (segment) => {
|
|
100
|
+
if (profileLabels.length === 0) profileLabels.push(0)
|
|
101
|
+
curvilinearOffset += length(segment, { units: 'kilometers' }) * 1000
|
|
102
|
+
profileLabels.push(Units.convert(curvilinearOffset, 'm', this.chartDistanceUnit))
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return allCoordsHaveHeight ? [profileHeights, profileLabels] : [[], []]
|
|
108
|
+
},
|
|
109
|
+
updateChart (terrainHeights, terrainLabels, profileHeights, profileLabels, chartWidth) {
|
|
110
|
+
const update = {
|
|
111
|
+
type: 'line',
|
|
112
|
+
data: { datasets: [] },
|
|
113
|
+
plugins: [{
|
|
114
|
+
// a simple plugin to display a vertical line at cursor position
|
|
115
|
+
beforeEvent: (chart, args) => {
|
|
116
|
+
if (args.event.type === 'mousemove') {
|
|
117
|
+
if ((args.event.x >= chart.chartArea.left) &&
|
|
118
|
+
(args.event.x <= chart.chartArea.right)) {
|
|
119
|
+
chart.config.options.vline.enabled = true
|
|
120
|
+
chart.config.options.vline.x = args.event.x
|
|
121
|
+
} else {
|
|
122
|
+
chart.config.options.vline.enabled = false
|
|
123
|
+
}
|
|
124
|
+
} else if (args.event.type === 'mouseout') {
|
|
125
|
+
chart.config.options.vline.enabled = false
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
afterDraw: (chart) => {
|
|
129
|
+
const x = chart.config.options.vline.x
|
|
130
|
+
const ctx = chart.ctx
|
|
131
|
+
if (chart.config.options.vline.enabled && !isNaN(x)) {
|
|
132
|
+
ctx.save()
|
|
133
|
+
ctx.translate(0.5, 0.5)
|
|
134
|
+
ctx.lineWidth = 1
|
|
135
|
+
ctx.strokeStyle = chart.config.options.vline.color
|
|
136
|
+
ctx.beginPath()
|
|
137
|
+
ctx.moveTo(x, chart.chartArea.bottom)
|
|
138
|
+
ctx.lineTo(x, chart.chartArea.top)
|
|
139
|
+
ctx.stroke()
|
|
140
|
+
ctx.restore()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}],
|
|
144
|
+
options: {
|
|
145
|
+
maintainAspectRatio: false,
|
|
146
|
+
// stepped: 'middle',
|
|
147
|
+
parsing: false, // because we'll provide data in chart native format
|
|
148
|
+
onHover: (context, elements) => {
|
|
149
|
+
// update marker highlight along profile
|
|
150
|
+
if (elements.length) {
|
|
151
|
+
const abscissa = _.get(elements[0].element, '$context.parsed.x')
|
|
152
|
+
if (abscissa !== undefined) {
|
|
153
|
+
let abscissaKm = Units.convert(abscissa, this.chartDistanceUnit, 'km')
|
|
154
|
+
let segment = this.feature
|
|
155
|
+
// handle multi line strings too, find segment in which abscissa is
|
|
156
|
+
if (_.get(this.feature, 'geometry.type') === 'MultiLineString') {
|
|
157
|
+
const lines = flatten(this.feature).features
|
|
158
|
+
for (let i = 0; i < lines.length && segment === this.feature; ++i) {
|
|
159
|
+
const len = length(lines[i], { units: 'kilometers' })
|
|
160
|
+
if (i !== lines.length - 1) {
|
|
161
|
+
if (abscissaKm > len) { abscissaKm -= len }
|
|
162
|
+
else { segment = lines[i] }
|
|
163
|
+
} else {
|
|
164
|
+
// last multi line segment, must be on this one
|
|
165
|
+
if (abscissaKm > len) { abscissaKm = len }
|
|
166
|
+
segment = lines[i]
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const feature = along(segment, abscissaKm, { units: 'kilometers' })
|
|
172
|
+
this.kActivity.updateSelectionHighlight('elevation-profile', feature)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// restore tooltip and vline if they've been disabled during
|
|
177
|
+
// pan or zoom animation
|
|
178
|
+
if (context.chart.config.options.plugins.tooltip.enabled) return
|
|
179
|
+
context.chart.config.options.plugins.tooltip.enabled = true
|
|
180
|
+
context.chart.config.options.vline.enabled = true
|
|
181
|
+
context.chart.update()
|
|
182
|
+
},
|
|
183
|
+
interaction: {
|
|
184
|
+
mode: 'xSingle',
|
|
185
|
+
intersect: false
|
|
186
|
+
},
|
|
187
|
+
scales: {
|
|
188
|
+
x: {
|
|
189
|
+
type: 'linear',
|
|
190
|
+
beginAtZero: true,
|
|
191
|
+
title: {
|
|
192
|
+
display: true,
|
|
193
|
+
text: this.$t('KElevationProfile.CURVILINEAR_AXIS_LEGEND', { unit: this.chartDistanceUnit })
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
y: {
|
|
197
|
+
type: 'linear',
|
|
198
|
+
beginAtZero: true,
|
|
199
|
+
title: {
|
|
200
|
+
display: true,
|
|
201
|
+
text: this.$t('KElevationProfile.HEIGHT_AXIS_LEGEND', { unit: this.chartHeightUnit })
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
vline: { // option values related to vertical line plugin defined inline
|
|
206
|
+
enabled: false,
|
|
207
|
+
x: 0,
|
|
208
|
+
color: 'black'
|
|
209
|
+
},
|
|
210
|
+
plugins: {
|
|
211
|
+
title: {
|
|
212
|
+
display: true,
|
|
213
|
+
text: this.title,
|
|
214
|
+
align: 'start'
|
|
215
|
+
},
|
|
216
|
+
legend: {
|
|
217
|
+
display: false
|
|
218
|
+
},
|
|
219
|
+
datalabels: {
|
|
220
|
+
display: false
|
|
221
|
+
},
|
|
222
|
+
tooltip: {
|
|
223
|
+
position: 'cursorPosition',
|
|
224
|
+
callbacks: {
|
|
225
|
+
/*
|
|
226
|
+
title: (context) => {
|
|
227
|
+
let title = `${context[0].parsed.x.toFixed(2)} ${this.chartDistanceUnit}`
|
|
228
|
+
return title
|
|
229
|
+
},
|
|
230
|
+
*/
|
|
231
|
+
label: (context) => {
|
|
232
|
+
let label = context.dataset.label || ''
|
|
233
|
+
if (label) label += ': '
|
|
234
|
+
if (context.parsed.y !== null) label += Units.format(context.parsed.y, this.chartHeightUnit)
|
|
235
|
+
return label
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
decimation: {
|
|
240
|
+
enabled: true,
|
|
241
|
+
algorithm: 'lttb',
|
|
242
|
+
// algorithm: 'min-max',
|
|
243
|
+
samples: Math.floor(chartWidth / 6),
|
|
244
|
+
threshold: Math.floor(chartWidth / 2)
|
|
245
|
+
},
|
|
246
|
+
zoom: {
|
|
247
|
+
pan: {
|
|
248
|
+
// pan with mouse and no modifiers
|
|
249
|
+
enabled: true,
|
|
250
|
+
// modifierKey: 'ctrl',
|
|
251
|
+
onPanStart: (context) => {
|
|
252
|
+
// robin: for some reason, pan starts even with some modifiers keys
|
|
253
|
+
// make sure here there's no modifiers here
|
|
254
|
+
const event = _.get(context, 'event.srcEvent')
|
|
255
|
+
const hasModifiers = event ? (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) : false
|
|
256
|
+
if (hasModifiers) return false
|
|
257
|
+
|
|
258
|
+
// hide tooltip & vline while zooming
|
|
259
|
+
context.chart.config.options.plugins.tooltip.enabled = false
|
|
260
|
+
context.chart.config.options.vline.enabled = false
|
|
261
|
+
return true
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
limits: {
|
|
265
|
+
x: { min: 'original', max: 'original' },
|
|
266
|
+
y: { min: 'original', max: 'original' }
|
|
267
|
+
},
|
|
268
|
+
zoom: {
|
|
269
|
+
// zoom with mouse + ctrl, or wheel
|
|
270
|
+
drag: {
|
|
271
|
+
enabled: true,
|
|
272
|
+
modifierKey: 'ctrl',
|
|
273
|
+
backgroundColor: colors.getBrand('secondary')
|
|
274
|
+
},
|
|
275
|
+
wheel: {
|
|
276
|
+
enabled: true
|
|
277
|
+
},
|
|
278
|
+
mode: 'x',
|
|
279
|
+
onZoomStart: (context) => {
|
|
280
|
+
// hide tooltip & vline while zooming
|
|
281
|
+
context.chart.config.options.plugins.tooltip.enabled = false
|
|
282
|
+
context.chart.config.options.vline.enabled = false
|
|
283
|
+
return true
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Add profile elevation if provided
|
|
292
|
+
if (profileHeights.length) {
|
|
293
|
+
update.data.datasets.push({
|
|
294
|
+
label: this.$t('KElevationProfile.PROFILE_CHART_LEGEND'),
|
|
295
|
+
data: profileHeights.map((h, i) => { return { x: profileLabels[i], y: h } }),
|
|
296
|
+
fill: false,
|
|
297
|
+
borderColor: '#51b0e8',
|
|
298
|
+
backgroundColor: '#0986bc',
|
|
299
|
+
pointRadius: 3
|
|
300
|
+
})
|
|
301
|
+
update.options.plugins.legend.display = true
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Add terrain elevation dataset
|
|
305
|
+
update.data.datasets.push({
|
|
306
|
+
label: this.$t('KElevationProfile.TERRAIN_CHART_LEGEND'),
|
|
307
|
+
data: terrainHeights.map((h, i) => { return { x: terrainLabels[i], y: h } }),
|
|
308
|
+
fill: true,
|
|
309
|
+
borderColor: '#635541',
|
|
310
|
+
backgroundColor: '#c9b8a1',
|
|
311
|
+
pointRadius: 2
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
this.$refs.chart.update(update)
|
|
315
|
+
},
|
|
316
|
+
|
|
317
|
+
async refresh () {
|
|
318
|
+
const maxResolution = 30
|
|
319
|
+
this.profile = null
|
|
320
|
+
this.refreshActions()
|
|
321
|
+
if (!this.layer || !this.feature) return
|
|
322
|
+
|
|
323
|
+
// Check supported geometry
|
|
324
|
+
const geometry = _.get(this.feature, 'geometry.type')
|
|
325
|
+
if (geometry !== 'LineString' && geometry !== 'MultiLineString') {
|
|
326
|
+
logger.warn('the selected feature has an invald geometry')
|
|
327
|
+
this.$toast({ type: 'negative', message: this.$t('KElevationProfile.INVALID_GEOMETRY') })
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const featureStyle = { properties: { 'marker-type': 'marker' } }
|
|
332
|
+
this.kActivity.addSelectionHighlight('elevation-profile', featureStyle)
|
|
333
|
+
|
|
334
|
+
this.chartDistanceUnit = 'm'
|
|
335
|
+
this.chartHeightUnit = Units.getDefaultUnit('altitude')
|
|
336
|
+
|
|
337
|
+
// TODO: this is the window size, not the widget size ...
|
|
338
|
+
const windowSize = this.$store.get('window.size')
|
|
339
|
+
const chartWidth = windowSize[0]
|
|
340
|
+
|
|
341
|
+
const queries = []
|
|
342
|
+
const resolution = _.get(this.feature, 'properties.elevationProfile.resolution')
|
|
343
|
+
const resolutionUnit = _.get(this.feature, 'properties.elevationProfile.resolutionUnit', 'm')
|
|
344
|
+
const corridor = _.get(this.feature, 'properties.elevationProfile.corridorWidth')
|
|
345
|
+
const corridorUnit = _.get(this.feature, 'properties.elevationProfile.corridorWidthUnit', 'm')
|
|
346
|
+
const securityMargin = _.get(this.feature, 'properties.elevationProfile.securityMargin')
|
|
347
|
+
const securityMarginUnit = _.get(this.feature, 'properties.elevationProfile.securityMarginUnit', 'm')
|
|
348
|
+
if (geometry === 'MultiLineString') {
|
|
349
|
+
flatten(this.feature).features.forEach((feature, index) => {
|
|
350
|
+
queries.push({
|
|
351
|
+
profile: feature,
|
|
352
|
+
resolution: Units.convert(resolution[index], resolutionUnit, 'm'),
|
|
353
|
+
corridorWidth: corridor ? Units.convert(corridor[index], corridorUnit, 'm') : null,
|
|
354
|
+
securityMargin: securityMargin ? Units.convert(securityMargin[index], securityMarginUnit, 'm') : null
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
} else {
|
|
358
|
+
const pixelStep = 5
|
|
359
|
+
const res = resolution ? Units.convert(resolution, resolutionUnit, 'm') : Math.max(length(this.feature, { units: 'kilometers' }) * 1000 / (chartWidth / pixelStep), maxResolution)
|
|
360
|
+
queries.push({
|
|
361
|
+
profile: this.feature,
|
|
362
|
+
resolution: res,
|
|
363
|
+
corridorWidth: corridor ? Units.convert(corridor, corridorUnit, 'm') : null,
|
|
364
|
+
securityMargin: securityMargin ? Units.convert(securityMargin, securityMarginUnit, 'm') : null
|
|
365
|
+
})
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Extract heights from profile if available
|
|
369
|
+
const [profileHeights, profileLabels] = this.extractProfileData(queries.map((q) => q.profile))
|
|
370
|
+
|
|
371
|
+
// Setup the request url options
|
|
372
|
+
const endpoint = this.$store.get('capabilities.api.gateway') + '/elevation'
|
|
373
|
+
const headers = { 'Content-Type': 'application/json' }
|
|
374
|
+
// Add the Authorization header if jwt is defined
|
|
375
|
+
const jwt = this.$api.get('storage').getItem(this.$config('gatewayJwt'))
|
|
376
|
+
if (jwt) headers.Authorization = 'Bearer ' + jwt
|
|
377
|
+
|
|
378
|
+
// Perform the requests
|
|
379
|
+
let dismiss = null
|
|
380
|
+
dismiss = this.$q.notify({
|
|
381
|
+
group: 'profile',
|
|
382
|
+
icon: 'las la-hourglass-half',
|
|
383
|
+
message: this.$t('KElevationProfile.COMPUTING_PROFILE'),
|
|
384
|
+
color: 'primary',
|
|
385
|
+
timeout: 0,
|
|
386
|
+
spinner: true
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
// Build a fetch per profile
|
|
390
|
+
const fetchs = []
|
|
391
|
+
for (const query of queries) {
|
|
392
|
+
fetchs.push(fetch(endpoint
|
|
393
|
+
+ `?resolution=${query.resolution}`
|
|
394
|
+
+ (query.corridorWidth ? `&corridorWidth=${query.corridorWidth}` : '')
|
|
395
|
+
+ (query.securityMargin ? `&elevationOffset=${query.securityMargin}` : ''), {
|
|
396
|
+
method: 'POST',
|
|
397
|
+
mode: 'cors',
|
|
398
|
+
body: JSON.stringify(query.profile),
|
|
399
|
+
headers
|
|
400
|
+
}))
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
let responses
|
|
404
|
+
try {
|
|
405
|
+
responses = await Promise.all(fetchs)
|
|
406
|
+
for (const res of responses) {
|
|
407
|
+
if (!res.ok) throw new Error('Fetch failed')
|
|
408
|
+
}
|
|
409
|
+
} catch (error) {
|
|
410
|
+
// Network error
|
|
411
|
+
dismiss()
|
|
412
|
+
this.$toast({ type: 'negative', message: this.$t('errors.NETWORK_ERROR') })
|
|
413
|
+
return
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
dismiss()
|
|
417
|
+
|
|
418
|
+
// Each profile will have a point on start and end points
|
|
419
|
+
// When we have multi line string, we skip the first point for all segment
|
|
420
|
+
// after the first one
|
|
421
|
+
let skipFirstPoint = false
|
|
422
|
+
const terrainHeights = []
|
|
423
|
+
const terrainLabels = []
|
|
424
|
+
let curvilinearOffset = 0
|
|
425
|
+
this.profile = []
|
|
426
|
+
for (let i = 0; i < queries.length; ++i) {
|
|
427
|
+
const points = await responses[i].json()
|
|
428
|
+
// Each point on the elevation profile will contains two properties;
|
|
429
|
+
// - z: the elevation in meters
|
|
430
|
+
// - t: the curvilinear abscissa relative to the queried profile in meters
|
|
431
|
+
points.features.forEach((point, index) => {
|
|
432
|
+
if (skipFirstPoint && index === 0) return
|
|
433
|
+
|
|
434
|
+
const clone = _.cloneDeep(point)
|
|
435
|
+
// Since we may have multiple profile with different query parameters
|
|
436
|
+
// offset t accordingly
|
|
437
|
+
clone.properties.t = Units.convert(curvilinearOffset + _.get(point, 'properties.t', 0), 'm', this.chartDistanceUnit)
|
|
438
|
+
this.profile.push(clone)
|
|
439
|
+
|
|
440
|
+
terrainHeights.push(Units.convert(point.properties.z, 'm', this.chartHeightUnit))
|
|
441
|
+
terrainLabels.push(clone.properties.t)
|
|
442
|
+
})
|
|
443
|
+
// Update curvilinear offset for next profile, and skip next profile's first point
|
|
444
|
+
// since it'll match with the current profile last point
|
|
445
|
+
curvilinearOffset += length(queries[i].profile, { units: 'kilometers' }) * 1000
|
|
446
|
+
skipFirstPoint = true
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
this.updateChart(terrainHeights, terrainLabels, profileHeights, profileLabels, chartWidth)
|
|
450
|
+
|
|
451
|
+
this.profile = featureCollection(this.profile)
|
|
452
|
+
|
|
453
|
+
// Refresh the actions
|
|
454
|
+
this.refreshActions()
|
|
455
|
+
},
|
|
456
|
+
onCenterOn () {
|
|
457
|
+
this.kActivity.centerOnSelection()
|
|
458
|
+
},
|
|
459
|
+
async onCopyProfile () {
|
|
460
|
+
if (this.profile) {
|
|
461
|
+
try {
|
|
462
|
+
await copyToClipboard(JSON.stringify(this.profile))
|
|
463
|
+
this.$toast({ type: 'positive', message: this.$t('KElevationProfile.PROFILE_COPIED') })
|
|
464
|
+
} catch (_) {
|
|
465
|
+
this.$toast({ type: 'negative', message: this.$t('KElevationProfile.CANNOT_COPY_PROFILE') })
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
onExportProfile () {
|
|
470
|
+
if (this.profile) {
|
|
471
|
+
const file = this.title + '.geojson'
|
|
472
|
+
const status = exportFile(file, JSON.stringify(this.profile))
|
|
473
|
+
if (status) this.$toast({ type: 'positive', message: this.$t('KElevationProfile.PROFILE_EXPORTED', { file }) })
|
|
474
|
+
else this.$toast({ type: 'negative', message: this.$t('KElevationProfile.CANNOT_EXPORT_PROFILE') })
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
beforeCreate () {
|
|
479
|
+
// laod the required components
|
|
480
|
+
this.$options.components['k-chart'] = this.$load('chart/KChart')
|
|
481
|
+
this.$options.components['k-panel'] = this.$load('frame/KPanel')
|
|
482
|
+
this.$options.components['k-stamp'] = this.$load('frame/KStamp')
|
|
483
|
+
},
|
|
484
|
+
beforeDestroy () {
|
|
485
|
+
this.kActivity.removeSelectionHighlight('elevation-profile')
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
</script>
|