Skip to content

Commit 2b38226

Browse files
authored
Merge pull request #24 from OpenGeoscience/metadata-filtering
Metadata Filtering
2 parents 483c566 + b5a178a commit 2b38226

36 files changed

Lines changed: 7197 additions & 108 deletions

client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"code": 130
8080
}
8181
],
82+
"vuejs-accessibility/click-events-have-key-events": "off",
8283
"vue/no-setup-props-destructure": 0,
8384
"sort-imports": [
8485
"error",

client/src/MapStore.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import {
22
Ref, computed, reactive, ref,
33
} from 'vue';
44
import {
5+
AnnotationTypes,
56
ClickedProps,
7+
ColorFilters,
68
Context,
79
Dataset,
810
LayerCollection,
@@ -24,7 +26,7 @@ async function isVectorBaseMapAvailable(vectorMapUrl: string) {
2426
type SideBarCard = 'indicators' | 'charts';
2527

2628
export default class MapStore {
27-
public static osmBaseMap = ref<'none' | 'osm-raster' | 'osm-vector'>('osm-vector');
29+
public static osmBaseMap = ref<'none' | 'osm-raster' | 'osm-vector'>('osm-raster');
2830

2931
public static userIsStaff = computed(() => !!UVdatApi.user?.is_staff);
3032

@@ -254,4 +256,31 @@ export default class MapStore {
254256
MapStore.sideBarCardSettings.value[key as SideBarCard].enabled = false;
255257
});
256258
};
259+
260+
public static vectorColorFilters: Ref<ColorFilters[]> = ref([]);
261+
262+
public static toggleColorFilter = (layerId: number, layerType: (AnnotationTypes | 'all'), key: string, value: string) => {
263+
const foundIndex = MapStore.vectorColorFilters.value.findIndex(
264+
(item) => item.layerId === layerId && layerType === item.layerType && key === item.key,
265+
);
266+
if (foundIndex === -1) {
267+
MapStore.vectorColorFilters.value.push({
268+
layerId,
269+
layerType,
270+
type: 'not in',
271+
key,
272+
values: new Set<string>([value]),
273+
});
274+
} else {
275+
const found = MapStore.vectorColorFilters.value[foundIndex];
276+
if (found.values.has(value)) {
277+
found.values.delete(value);
278+
if (found.values.size === 0) {
279+
MapStore.vectorColorFilters.value.splice(foundIndex, 1);
280+
}
281+
} else {
282+
found.values.add(value);
283+
}
284+
}
285+
};
257286
}

client/src/api/UVDATApi.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,4 +538,27 @@ export default class UVdatApi {
538538
});
539539
return response.data;
540540
}
541+
542+
public static async getMetadataFilters(): Promise<Record<string, string[]>> {
543+
return (await UVdatApi.apiClient.get('/metadata-filters/get_filters/')).data;
544+
}
545+
546+
public static async filterOnMetadata(
547+
metdataFilters: Record<string, string[]>,
548+
search?: string,
549+
): Promise<{ id: number, type: AbstractMapLayer['type'], matches: string[], name: string }[]> {
550+
return (await UVdatApi.apiClient.post('metadata-filters/filter_layers/', { filters: metdataFilters, search })).data;
551+
}
552+
553+
public static async getMapLayerList(
554+
layerIds: number[],
555+
layerTypes : AbstractMapLayer['type'][],
556+
): Promise<(VectorMapLayer | RasterMapLayer | NetCDFLayer)[]> {
557+
const params = new URLSearchParams();
558+
559+
layerIds.forEach((id) => params.append('mapLayerIds', id.toString()));
560+
layerTypes.forEach((id) => params.append('mapLayerTypes', id.toString()));
561+
562+
return (await UVdatApi.apiClient.get('/map-layers/', { params })).data;
563+
}
541564
}

client/src/components/FeatureSelection/vectorFeatureGraphUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const renderVectorFeatureGraph = (
6161

6262
y.domain(d3.extent(allYValues) as [number, number]);
6363

64-
const maxYValue = Math.max(...allYValues);
64+
const maxYValue = Math.max(allYValues);
6565
const maxYLabel = maxYValue.toFixed(2); // Format to 2 decimal places
6666
const maxCharacters = maxYLabel.length;
6767

client/src/components/LayerTypeConfig.vue

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export default defineComponent({
5454
return enabled;
5555
});
5656
57-
type LayerActionItems = 'enabled' | 'selectable' | 'hoverable' | 'opacity' | 'zoomMinMax' | 'selectColor' | 'defaultSize' | 'legend' | 'color' | 'text' | 'heatmapControls';
57+
type LayerActionItems = 'enabled' | 'selectable' | 'hoverable' | 'opacity' | 'zoomMinMax' | 'selectColor' | 'defaultSize' | 'legend' | 'color' | 'text' | 'heatmapControls' | 'drawPoints';
5858
const layerActionItemsMap: Record<LayerActionItems, AnnotationTypes[]> = {
5959
enabled: ['line', 'fill', 'circle', 'fill-extrusion', 'text', 'heatmap'],
6060
selectable: ['line', 'fill', 'circle', 'fill-extrusion'],
@@ -67,11 +67,12 @@ export default defineComponent({
6767
color: ['line', 'fill', 'circle', 'fill-extrusion', 'text'],
6868
text: ['text'],
6969
heatmapControls: ['heatmap'],
70+
drawPoints: ['line'],
7071
};
7172
7273
const actionItemVisible = computed(() => {
7374
const enabledItems = new Set<LayerActionItems>();
74-
const itemList: LayerActionItems[] = ['enabled', 'selectable', 'hoverable', 'legend', 'opacity', 'zoomMinMax', 'selectColor', 'defaultSize', 'color', 'text', 'heatmapControls'];
75+
const itemList: LayerActionItems[] = ['enabled', 'selectable', 'hoverable', 'legend', 'opacity', 'zoomMinMax', 'selectColor', 'defaultSize', 'color', 'text', 'heatmapControls', 'drawPoints'];
7576
itemList.forEach((key) => {
7677
if (layerActionItemsMap[key].includes(props.layerType)) {
7778
enabledItems.add(key);
@@ -102,6 +103,10 @@ export default defineComponent({
102103
if (field === 'legend') {
103104
displayConfig.legend = val;
104105
}
106+
if (field === 'drawPoints') {
107+
displayConfig.drawPoints = val;
108+
}
109+
105110
if (field === 'opacity') {
106111
if (val) {
107112
displayConfig.opacity = 0.75;
@@ -683,6 +688,32 @@ export default defineComponent({
683688
</v-tooltip>
684689
</v-col>
685690
</v-row>
691+
<v-row
692+
v-if="actionItemVisible.has('drawPoints')"
693+
dense
694+
align="center"
695+
justify="center"
696+
>
697+
<v-col cols="2">
698+
<v-tooltip text="Draw Points">
699+
<template #activator="{ props }">
700+
<v-icon
701+
class="pl-3"
702+
v-bind="props"
703+
>
704+
mdi-circle-outline
705+
</v-icon>
706+
</template>
707+
</v-tooltip>
708+
</v-col>
709+
<v-col>
710+
<v-icon @click="updateLayerTypeField('drawPoints', !valueDisplayCheckbox('drawPoints'))">
711+
{{
712+
valueDisplayCheckbox('drawPoints') ? 'mdi-checkbox-marked' : 'mdi-checkbox-blank-outline' }}
713+
</v-icon>
714+
<span class="pl-2">Draw Points</span>
715+
</v-col>
716+
</v-row>
686717
<div v-if="actionItemVisible.has('heatmapControls')">
687718
<heatmap-layer-controls :layer-id="layerId" :layer-type="layerType" />
688719
</div>

client/src/components/Map.vue

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Protocol as PMTilesProtocol } from 'pmtiles';
55
import {
66
Ref, defineComponent, onMounted, ref, watch,
77
} from 'vue';
8-
import MapStore, { VECTOR_PMTILES_URL } from '../MapStore';
8+
import MapStore from '../MapStore';
99
import {
1010
updateSelected,
1111
} from '../map/mapVectorLayers';
@@ -81,10 +81,6 @@ export default defineComponent({
8181
tileSize: 256,
8282
attribution: '© OpenStreetMap contributors',
8383
},
84-
[OSM_VECTOR_ID]: {
85-
type: 'vector',
86-
url: `pmtiles://${VECTOR_PMTILES_URL}`,
87-
},
8884
'tva-region': {
8985
type: 'geojson',
9086
data: TVA_GEOJSON,
@@ -125,7 +121,6 @@ export default defineComponent({
125121
minzoom: 0,
126122
maxzoom: 19,
127123
},
128-
...VECTOR_LAYERS,
129124
{
130125
id: 'naip-imagery-tiles',
131126
type: 'raster',

client/src/components/MapLegends/ColorKey.vue

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<!-- eslint-disable vuejs-accessibility/click-events-have-key-events -->
12
<!-- eslint-disable vue/max-len -->
23
<script lang="ts">
34
import {
@@ -20,6 +21,7 @@ import {
2021
} from '../../types'; // Import your defined types
2122
import { createColorNumberPairs, formatNumPrecision, getLayerAvailableProperties } from '../../utils';
2223
import MapStore from '../../MapStore';
24+
import { updateLayerFilter } from '../../map/mapVectorLayers';
2325
2426
export default defineComponent({
2527
name: 'ColorKey',
@@ -98,13 +100,20 @@ export default defineComponent({
98100
} else if (
99101
layerDisplayConfig.color.type === 'ColorCategoricalString'
100102
) {
103+
const found = MapStore.vectorColorFilters.value.find((item) => item.layerId === layer.id && item.layerType === 'all' && item.key === (layerDisplayConfig.color as ColorCategoricalString).attribute);
101104
keyType.colors.push({
102105
type: 'categorical',
103106
attribute: (layerDisplayConfig.color as ColorCategoricalString).attribute,
104107
pairs: Object.entries(
105108
(layerDisplayConfig.color as ColorCategoricalString)
106109
.colorPairs,
107-
).map(([key, value]) => ({ value: key, color: value })),
110+
).map(([key, value]) => {
111+
let disabled;
112+
if (found && found.values.has(key)) {
113+
disabled = true;
114+
}
115+
return { value: key, color: value, disabled };
116+
}),
108117
});
109118
} else if (
110119
layerDisplayConfig.color.type === 'ColorCategoricalNumber'
@@ -312,6 +321,14 @@ export default defineComponent({
312321
expandedPanels.value = opened;
313322
setTimeout(() => drawGradients(), 100);
314323
}, { immediate: true });
324+
325+
const toggleBoxColorFilter = (layerId: number, layerType: AnnotationTypes | 'all', key: string, value: string) => {
326+
MapStore.toggleColorFilter(layerId, layerType, key, value);
327+
const foundVectorLayer = MapStore.selectedVectorMapLayers.value.find((item) => item.id === layerId);
328+
if (foundVectorLayer) {
329+
updateLayerFilter(foundVectorLayer);
330+
}
331+
};
315332
return {
316333
capitalize,
317334
processedLayers,
@@ -321,6 +338,7 @@ export default defineComponent({
321338
drawDelay,
322339
iconMapper,
323340
expandedPanels,
341+
toggleBoxColorFilter,
324342
};
325343
},
326344
});
@@ -391,12 +409,20 @@ export default defineComponent({
391409
justify="center"
392410
>
393411
<v-col>
394-
<span>{{ pair.value }}: </span>
412+
<span>{{ pair.value }}:</span>
395413
</v-col>
396414
<v-col cols="1">
397415
<div
416+
v-if="!pair.disabled"
398417
class="color-icon"
399418
:style="{ backgroundColor: pair.color }"
419+
@click="toggleBoxColorFilter(layer.id, 'all', colorConfig.attribute, pair.value)"
420+
/>
421+
<div
422+
v-else
423+
class="color-icon"
424+
:style="`border: 3px solid ${pair.color}`"
425+
@click="toggleBoxColorFilter(layer.id, 'all', colorConfig.attribute, pair.value)"
400426
/>
401427
</v-col>
402428
</v-row>
@@ -484,7 +510,11 @@ export default defineComponent({
484510
:key="`${layer.id}_${keyType.type}_${index}`"
485511
>
486512
<v-expansion-panel-title style="font-size:0.75em">
487-
<span v-if="!['netCDF', 'raster'].includes(keyType.type)"><v-icon v-if="iconMapper[keyType.type]" class="pr-2"> {{ iconMapper[keyType.type] }}</v-icon>{{ capitalize(keyType.type) }}</span>
513+
<span v-if="!['netCDF', 'raster'].includes(keyType.type)">
514+
<v-icon v-if="iconMapper[keyType.type]" class="pr-2">
515+
{{ iconMapper[keyType.type] }}
516+
</v-icon>{{ capitalize(keyType.type) }}
517+
</span>
488518
<span v-else-if="colorConfig.type === 'linearNetCDF'">{{ capitalize(colorConfig.value) }}</span>
489519
<span v-if="['categorical', 'linear'].includes(colorConfig.type)">:
490520
{{ attributeValues[layer.id][colorConfig.attribute].displayName }}
@@ -511,8 +541,16 @@ export default defineComponent({
511541
</v-col>
512542
<v-col cols="1">
513543
<div
544+
v-if="!pair.disabled"
514545
class="color-icon"
515546
:style="{ backgroundColor: pair.color }"
547+
@click.stop="toggleBoxColorFilter(layer.id, 'all', colorConfig.attribute, pair.value)"
548+
/>
549+
<div
550+
v-else
551+
class="color-icon"
552+
:style="`border: 3px solid ${pair.color}`"
553+
@click.stop="toggleBoxColorFilter(layer.id, 'all', colorConfig.attribute, pair.value)"
516554
/>
517555
</v-col>
518556
</v-row>
@@ -602,4 +640,8 @@ export default defineComponent({
602640
height: 15px;
603641
border: 1px solid gray;
604642
}
643+
.color-icon:hover {
644+
cursor: pointer;
645+
}
646+
605647
</style>

client/src/components/MapLegends/ControlsKey.vue

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,12 @@ export default defineComponent({
7272
});
7373
// Compute NetCDF Layer Keys
7474
const stepIndexMap: Record<string, { length: number, currentIndex: number }> = {};
75+
const resamplingMap: Record<string, 'linear' | 'nearest'> = {};
7576
visibleNetCDFLayers.value.forEach((item) => {
7677
const found = props.netcdfLayers.find((layer) => layer.id === item.netCDFLayer);
7778
if (found) {
7879
const { opacity } = item;
80+
resamplingMap[`netcdf_${found.id}`] = item.resampling === 'nearest' ? 'nearest' : 'linear';
7981
mapLayerOpacityMap[`netcdf_${found.id}`] = opacity !== undefined ? opacity : 1.0;
8082
stepIndexMap[`netcdf_${found.id}`] = {
8183
length: item.images.length,
@@ -87,7 +89,7 @@ export default defineComponent({
8789
props.rasterLayers.forEach((layer) => {
8890
mapLayerOpacityMap[`raster_${layer.id}`] = layer?.default_style?.opacity !== undefined ? layer.default_style.opacity : 1.0;
8991
});
90-
const order: { id: number, opacity: number, name: string, type: 'netcdf' | 'vector' | 'raster', length?: number, currentIndex?: number } [] = [];
92+
const order: { id: number, opacity: number, name: string, type: 'netcdf' | 'vector' | 'raster', length?: number, currentIndex?: number, resampling?: 'linear' | 'nearest' } [] = [];
9193
MapStore.selectedMapLayers.value.forEach((layer) => {
9294
if (layer.type === 'netcdf') {
9395
if (mapLayerOpacityMap[`netcdf_${layer.id}`] !== undefined) {
@@ -99,7 +101,7 @@ export default defineComponent({
99101
currentIndex = data.currentIndex;
100102
}
101103
order.push({
102-
id: layer.id, opacity: mapLayerOpacityMap[`netcdf_${layer.id}`], name: layer.name, type: layer.type, length, currentIndex,
104+
id: layer.id, opacity: mapLayerOpacityMap[`netcdf_${layer.id}`], name: layer.name, type: layer.type, length, currentIndex, resampling: resamplingMap[`netcdf_${layer.id}`],
103105
});
104106
}
105107
} else if (layer.type === 'raster') {
@@ -155,7 +157,7 @@ export default defineComponent({
155157
});
156158
157159
const updateIndex = (layerId: number, currentIndex: number) => {
158-
updateNetCDFLayer(layerId, currentIndex);
160+
updateNetCDFLayer(layerId, { index: currentIndex });
159161
};
160162
const throttledUpdateNetCDFLayer = throttle(updateIndex, 50);
161163
@@ -175,7 +177,7 @@ export default defineComponent({
175177
const found = visibleNetCDFLayers.value.find((layer) => item.id === layer.netCDFLayer);
176178
if (found) {
177179
found.opacity = val;
178-
updateNetCDFLayer(item.id, undefined, val);
180+
updateNetCDFLayer(item.id, { opacity: val });
179181
}
180182
}
181183
if (item.type === 'raster') {
@@ -190,12 +192,22 @@ export default defineComponent({
190192
}
191193
}
192194
};
195+
196+
const toggleResampling = (id: number) => {
197+
const found = visibleNetCDFLayers.value.find((layer) => id === layer.netCDFLayer);
198+
if (found) {
199+
const val = found.resampling === 'linear' ? 'nearest' : 'linear';
200+
found.resampling = val;
201+
updateNetCDFLayer(id, { resampling: val });
202+
}
203+
};
193204
return {
194205
processedLayers,
195206
iconMapper,
196207
updateOpacity,
197208
throttledUpdateNetCDFLayer,
198209
stepMapping,
210+
toggleResampling,
199211
};
200212
},
201213
});
@@ -211,7 +223,14 @@ export default defineComponent({
211223
>
212224
<v-card-text class="py-1 px-2">
213225
<span class="py-1 px-2 d-flex align-center">
214-
<v-icon size="16" class="mr-1" color="primary">
226+
<v-tooltip v-if="item.type === 'netcdf'" text="Image Scaling (Nearest vs Linear)">
227+
<template #activator="{ props }">
228+
<v-icon size="16" v-bind="props" class="mr-1" color="primary" @click="toggleResampling(item.id)">
229+
{{ item.resampling === 'nearest' ? ' mdi-view-grid' : 'mdi-grid' }}
230+
</v-icon>
231+
</template>
232+
</v-tooltip>
233+
<v-icon v-else size="16" class="mr-1" color="primary">
215234
{{ iconMapper[item.type] }}
216235
</v-icon>
217236
<span class="text-sm">{{ item.name }}</span>

0 commit comments

Comments
 (0)