Skip to content

Commit 6839557

Browse files
committed
client side beginnings
1 parent 4e0ee68 commit 6839557

17 files changed

Lines changed: 1023 additions & 42 deletions

client/src/MapStore.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
Context,
99
Dataset,
1010
DisplayConfiguration,
11+
FMVLayer,
1112
LayerCollection,
1213
NetCDFData,
1314
NetCDFImageWorking,
@@ -66,9 +67,9 @@ export default class MapStore {
6667
public static datasetsByContext = reactive<Record<number, Dataset[]>>({});
6768

6869
// Layers
69-
public static mapLayersByDataset = reactive<Record<number, (VectorMapLayer | RasterMapLayer | NetCDFData)[]>>({});
70+
public static mapLayersByDataset = reactive<Record<number, (VectorMapLayer | RasterMapLayer | NetCDFData | FMVLayer)[]>>({});
7071

71-
public static selectedMapLayers = ref<(VectorMapLayer | RasterMapLayer | NetCDFLayer)[]>([]);
72+
public static selectedMapLayers = ref<(VectorMapLayer | RasterMapLayer | NetCDFLayer | FMVLayer)[]>([]);
7273

7374
public static visibleMapLayers: Ref<Set<string>> = ref(new Set());
7475

@@ -88,6 +89,10 @@ export default class MapStore {
8889
() => this.selectedMapLayers.value.filter((layer) => layer.type === 'netcdf'),
8990
);
9091

92+
public static selectedFMVMapLayers: Ref<FMVLayer[]> = computed(
93+
() => this.selectedMapLayers.value.filter((layer) => layer.type === 'fmv'),
94+
);
95+
9196
public static async loadCollections() {
9297
MapStore.availableCollections.value = await UVdatApi.getLayerCollections();
9398
}
@@ -121,8 +126,8 @@ export default class MapStore {
121126
if (initial && MapStore.displayConfiguration.value.default_displayed_layers.length) {
122127
const datasetIds = MapStore.displayConfiguration.value.default_displayed_layers.map((item) => item.dataset_id);
123128
const datasetIdLayers = await UVdatApi.getDatasetsLayers(datasetIds);
124-
const layerByDataset: Record<number, (VectorMapLayer | RasterMapLayer | NetCDFData)[]> = {};
125-
const toggleLayers: (VectorMapLayer | RasterMapLayer | NetCDFLayer)[] = [];
129+
const layerByDataset: Record<number, (VectorMapLayer | RasterMapLayer | NetCDFData | FMVLayer)[]> = {};
130+
const toggleLayers: (VectorMapLayer | RasterMapLayer | NetCDFLayer | FMVLayer)[] = [];
126131
const enabledLayers = MapStore.displayConfiguration.value.default_displayed_layers;
127132
datasetIdLayers.forEach((item) => {
128133
if (item.dataset_id !== undefined) {

client/src/api/UVDATApi.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {
1414
FeatureGraphs,
1515
FeatureGraphsRequest,
1616
FileItem,
17+
FMVLayer,
18+
FMVLayerData,
1719
LayerCollection,
1820
LayerCollectionLayer,
1921
LayerRepresentation,
@@ -363,7 +365,7 @@ export default class UVdatApi {
363365
public static async getMapLayerCollectionList(
364366
layers: LayerCollectionLayer[],
365367
enabled? : boolean,
366-
): Promise<(VectorMapLayer | RasterMapLayer | NetCDFLayer)[]> {
368+
): Promise<(VectorMapLayer | RasterMapLayer | NetCDFLayer | FMVLayer)[]> {
367369
return (await UVdatApi.apiClient.post('/map-layers/', { layers }, { params: { enabled } })).data;
368370
}
369371

@@ -375,6 +377,10 @@ export default class UVdatApi {
375377
return (await UVdatApi.apiClient.get(`/vectors/${mapLayerId}/bbox`)).data;
376378
}
377379

380+
public static async getFMVBbox(mapLayerId: number): Promise<Bounds> {
381+
return (await UVdatApi.apiClient.get(`/fmv-layer/${mapLayerId}/bbox`)).data;
382+
}
383+
378384
public static async getMapLayersBoundingBox(
379385
rasterMapLayerIds: number[] = [],
380386
vectorMapLayerIds: number[] = [],
@@ -626,7 +632,7 @@ export default class UVdatApi {
626632
public static async getMapLayerList(
627633
layerIds: number[],
628634
layerTypes : AbstractMapLayer['type'][],
629-
): Promise<(VectorMapLayer | RasterMapLayer | NetCDFLayer)[]> {
635+
): Promise<(VectorMapLayer | RasterMapLayer | NetCDFLayer | FMVLayer)[]> {
630636
const params = new URLSearchParams();
631637

632638
layerIds.forEach((id) => params.append('mapLayerIds', id.toString()));
@@ -663,4 +669,8 @@ export default class UVdatApi {
663669
const response = await UVdatApi.apiClient.patch('display-configuration/', config);
664670
return response.data;
665671
}
672+
673+
public static async getFMVLayerData(layerId: number): Promise<FMVLayerData> {
674+
return (await UVdatApi.apiClient.get(`/fmv-layer/${layerId}/`)).data;
675+
}
666676
}

client/src/components/DataSelection/DatasetItem.vue

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import {
55
onUnmounted,
66
ref,
77
} from 'vue';
8-
import { NetCDFData, RasterMapLayer, VectorMapLayer } from '../../types';
8+
import {
9+
FMVLayer, NetCDFData, RasterMapLayer, VectorMapLayer,
10+
} from '../../types';
911
import { toggleLayerSelection } from '../../map/mapLayers';
1012
import MapStore from '../../MapStore';
1113
import NetCDFDataConfigurator from './NetCDFDataConfigurator.vue';
@@ -16,7 +18,7 @@ export default defineComponent({
1618
},
1719
props: {
1820
layer: {
19-
type: Object as PropType<RasterMapLayer | VectorMapLayer | NetCDFData>,
21+
type: Object as PropType<RasterMapLayer | VectorMapLayer | NetCDFData | FMVLayer>,
2022
required: true,
2123
},
2224
},
@@ -71,7 +73,7 @@ export default defineComponent({
7173

7274
<template>
7375
<v-checkbox
74-
v-if="layer.type === 'raster' || layer.type === 'vector'"
76+
v-if="layer.type === 'raster' || layer.type === 'vector' || layer.type === 'fmv'"
7577
:model-value="!!selectedLayers.find((item) => (item.id === layer.id))"
7678
class="layer-checkbox"
7779
density="compact"
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
<script lang="ts">
2+
import {
3+
PropType, computed, defineComponent, onMounted, ref, watch,
4+
} from 'vue';
5+
import { FMVStore, FMVVectorTypes, getFMVStore } from '../map/fmvStore';
6+
import { FMVLayer } from '../types';
7+
import { updateFMVLayer, updateFMVVideoMapping } from '../map/mapFMVLayer';
8+
9+
export default defineComponent({
10+
props: {
11+
layer: {
12+
type: Object as PropType<FMVLayer>,
13+
required: true,
14+
},
15+
},
16+
setup(props) {
17+
const fmvStore = ref<FMVStore | null>(null);
18+
const loaded = ref(false);
19+
const totalFrames = ref(0);
20+
const allProperties = ['flight_path', 'ground_frame', 'ground_union', 'video'];
21+
watch(() => fmvStore.value?.videoFrame, (newFrame) => {
22+
if (newFrame && fmvStore.value) {
23+
fmvStore.value.frameId = newFrame;
24+
updateFMVVideoMapping(props.layer);
25+
}
26+
});
27+
const frameId = computed<number>({
28+
get: () => fmvStore.value?.frameId ?? 0,
29+
set: (val) => {
30+
if (fmvStore.value) {
31+
fmvStore.value.frameId = val;
32+
updateFMVLayer(props.layer);
33+
updateFMVVideoMapping(props.layer);
34+
}
35+
},
36+
});
37+
38+
const visibleProperties = computed<(FMVVectorTypes | 'video')[]>({
39+
get: () => fmvStore.value?.visibleProperties ?? [],
40+
set: (val) => {
41+
if (fmvStore.value) {
42+
fmvStore.value.visibleProperties = val;
43+
updateFMVLayer(props.layer);
44+
}
45+
},
46+
});
47+
48+
const filterFrameStatus = computed<boolean>({
49+
get: () => fmvStore.value?.filterFrameStatus ?? false,
50+
set: (val) => {
51+
if (fmvStore.value) {
52+
fmvStore.value.filterFrameStatus = val;
53+
updateFMVLayer(props.layer);
54+
}
55+
},
56+
});
57+
58+
onMounted(async () => {
59+
fmvStore.value = await getFMVStore(props.layer.id);
60+
if (fmvStore.value) {
61+
totalFrames.value = fmvStore.value.videoData.totalFrames ?? 0;
62+
loaded.value = true;
63+
}
64+
});
65+
66+
const isPlaying = computed(() => !fmvStore.value?.videoSource?.paused);
67+
68+
function togglePlayback() {
69+
const video = fmvStore.value?.videoSource;
70+
if (!video) return;
71+
if (video.paused) {
72+
video.play();
73+
} else {
74+
video.pause();
75+
}
76+
}
77+
78+
function seekFrames(offset: number) {
79+
const store = fmvStore.value;
80+
if (!store) return;
81+
store.seekFrames(offset);
82+
updateFMVVideoMapping(props.layer);
83+
}
84+
85+
return {
86+
frameId,
87+
visibleProperties,
88+
filterFrameStatus,
89+
totalFrames,
90+
allProperties,
91+
loaded,
92+
isPlaying,
93+
togglePlayback,
94+
seekFrames,
95+
96+
};
97+
},
98+
});
99+
</script>
100+
101+
<template>
102+
<v-card-title>FMV Layer Controls</v-card-title>
103+
<v-card-text v-if="loaded">
104+
<v-row dense align="center" class="icon-row mb-2">
105+
<v-col class="d-flex align-center gap-2">
106+
<v-btn icon variant="plain" size="small" @click="seekFrames(-frameId)">
107+
<v-icon>mdi-skip-backward</v-icon>
108+
</v-btn>
109+
<v-btn icon variant="plain" size="small" @click="seekFrames(-1)">
110+
<v-icon>mdi-step-backward</v-icon>
111+
</v-btn>
112+
<v-btn icon variant="plain" size="small" @click="togglePlayback">
113+
<v-icon>{{ isPlaying ? 'mdi-pause' : 'mdi-play' }}</v-icon>
114+
</v-btn>
115+
<v-btn icon variant="plain" size="small" @click="seekFrames(1)">
116+
<v-icon>mdi-step-forward</v-icon>
117+
</v-btn>
118+
<v-btn icon variant="plain" size="small" @click="seekFrames(totalFrames - frameId)">
119+
<v-icon>mdi-skip-forward</v-icon>
120+
</v-btn>
121+
</v-col>
122+
</v-row>
123+
<!-- Frame ID Slider -->
124+
<v-row dense align="center">
125+
<v-col cols="3">
126+
<span>Frame ID:</span>
127+
</v-col>
128+
<v-col>
129+
<v-slider
130+
v-model="frameId"
131+
:min="0"
132+
:max="totalFrames"
133+
step="1"
134+
thumb-label
135+
:disabled="totalFrames === 0"
136+
/>
137+
</v-col>
138+
<v-col cols="2">
139+
<span>{{ frameId }}</span>
140+
</v-col>
141+
</v-row>
142+
143+
<!-- Visible Properties Multi-select -->
144+
<v-row dense align="center">
145+
<v-col cols="3">
146+
<span>Visible Properties:</span>
147+
</v-col>
148+
<v-col>
149+
<v-select
150+
v-model="visibleProperties"
151+
:items="allProperties"
152+
label="Visible Properties"
153+
multiple
154+
chips
155+
clearable
156+
/>
157+
</v-col>
158+
</v-row>
159+
160+
<!-- Filter by Frame Checkbox -->
161+
<v-row dense align="center">
162+
<v-col cols="3">
163+
<span>Filter by Frame:</span>
164+
</v-col>
165+
<v-col>
166+
<v-checkbox
167+
v-model="filterFrameStatus"
168+
label="Enable Frame Filtering"
169+
/>
170+
</v-col>
171+
</v-row>
172+
</v-card-text>
173+
<v-card-text v-else>
174+
Loading FMV Layer controls...
175+
</v-card-text>
176+
</template>
177+
178+
<style scoped>
179+
.tab {
180+
border: 1px solid lightgray;
181+
}
182+
183+
.tab:hover {
184+
cursor: pointer;
185+
}
186+
187+
.selected-tab {
188+
background-color: lightgray;
189+
}
190+
191+
.icon-center {
192+
width: 35px;
193+
height: 35px;
194+
border: 1px solid lightgray;
195+
display: flex;
196+
align-items: center;
197+
justify-content: center;
198+
}
199+
</style>

client/src/components/LayerConfig.vue

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,24 @@ import {
44
} from 'vue';
55
import MapStore from '../MapStore';
66
import {
7+
FMVLayer,
78
LayerRepresentation, NetCDFLayer, RasterMapLayer, VectorMapLayer,
89
} from '../types';
910
import RasterLayerConfig from './RasterLayerConfig.vue';
1011
import VectorLayerConfig from './VectorLayerConfig.vue';
12+
import FMVLayerConfig from './FMVLayerConfig.vue';
1113
import LayerRepresentationVue from './LayerRepresentation/LayerRepresentation.vue';
1214
import UVdatApi from '../api/UVDATApi';
1315
import { zoomToBounds } from '../map/mapLayers';
1416
import NetCDFLayerConfig from './NetCDFLayerConfig.vue';
1517
1618
export default defineComponent({
1719
components: {
18-
RasterLayerConfig, VectorLayerConfig, LayerRepresentationVue, NetCDFLayerConfig,
20+
RasterLayerConfig, VectorLayerConfig, LayerRepresentationVue, NetCDFLayerConfig, FMVLayerConfig,
1921
},
2022
props: {
2123
layer: {
22-
type: Object as PropType<(VectorMapLayer | RasterMapLayer | NetCDFLayer)>,
24+
type: Object as PropType<(VectorMapLayer | RasterMapLayer | NetCDFLayer | FMVLayer)>,
2325
required: true,
2426
},
2527
},
@@ -75,6 +77,9 @@ export default defineComponent({
7577
} else if (props.layer.type === 'vector') {
7678
const bounds = await UVdatApi.getVectorBbox(props.layer.id);
7779
zoomToBounds(bounds);
80+
} else if (props.layer.type === 'fmv') {
81+
const bounds = await UVdatApi.getFMVBbox(props.layer.id);
82+
zoomToBounds(bounds);
7883
} else if (props.layer.type === 'netcdf') {
7984
const { bounds } = (props.layer as NetCDFLayer);
8085
zoomToBounds(bounds);
@@ -174,11 +179,12 @@ export default defineComponent({
174179
/>
175180
<raster-layer-config v-if="proMode && layer.type === 'raster'" :layer="layer" />
176181
<vector-layer-config v-if="proMode && layer.type === 'vector'" :layer="layer" />
182+
<FMVLayerConfig v-if="proMode && layer.type === 'fmv'" :layer="layer" />
177183
<NetCDFLayerConfig
178184
v-if="layer.type === 'netcdf'"
179185
:layer="layer"
180186
/>
181-
<v-card-actions v-if="proMode">
187+
<v-card-actions v-if="proMode && layer.type !== 'fmv'">
182188
<v-row dense>
183189
<v-spacer />
184190
<v-tooltip :text="`Save: ${selectedLayerRep.name}`" location="top">

0 commit comments

Comments
 (0)