Skip to content

Commit 4deee4c

Browse files
authored
Merge pull request #720 from SlideRuleEarth/carlos-dev4
Use geo parquet as the primary format for the files as opposed to pla…
2 parents 6d471e0 + 3ef44f4 commit 4deee4c

33 files changed

+1438
-241
lines changed

web-client/.eslintrc.cjs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,27 @@ require('@rushstack/eslint-patch/modern-module-resolution')
33

44
module.exports = {
55
root: true,
6-
'extends': [
6+
extends: [
77
'plugin:vue/vue3-essential',
88
'eslint:recommended',
9-
'@vue/eslint-config-typescript',
109
'@vue/eslint-config-prettier/skip-formatting'
1110
],
11+
parser: 'vue-eslint-parser',
1212
parserOptions: {
13-
ecmaVersion: 'latest'
13+
ecmaVersion: 'latest',
14+
parser: '@typescript-eslint/parser',
15+
sourceType: 'module',
16+
project: './tsconfig.app.json',
17+
tsconfigRootDir: __dirname,
18+
extraFileExtensions: ['.vue']
19+
},
20+
plugins: ['@typescript-eslint'],
21+
rules: {
22+
// Catch async functions called without await
23+
'@typescript-eslint/no-floating-promises': 'error',
24+
// Warn about async functions that don't use await
25+
'require-await': 'warn',
26+
// Require functions that return promises to be marked async
27+
'@typescript-eslint/promise-function-async': 'warn'
1428
}
1529
}

web-client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"dev": "vite",
1515
"build-only": "vite build",
1616
"typecheck": "vue-tsc --noEmit -p tsconfig.app.json",
17-
"lint": "eslint .",
17+
"lint": "ESLINT_USE_FLAT_CONFIG=false eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts",
1818
"preview": "vite preview",
1919
"build": "run-p \"build-only {@}\" --",
2020
"build_with_maps": "VITE_BUILD_SOURCEMAP=true run-p \"build-only {@}\" --",

web-client/src/components/SrAnalysisMap.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
import { OL_DECK_LAYER_NAME } from '@/types/SrTypes';
3636
import { useAnalysisMapStore } from "@/stores/analysisMapStore";
3737
import { useGlobalChartStore } from "@/stores/globalChartStore";
38-
import { useDeck3DConfigStore } from "@/stores/deck3DConfigStore";
3938
import { callPlotUpdateDebounced } from '@/utils/plotUtils';
4039
import { setCyclesGtsSpotsFromFileUsingRgtYatc,updateSrViewName } from "@/utils/SrMapUtils";
4140
import Checkbox from 'primevue/checkbox';
@@ -68,10 +67,10 @@
6867
const srParquetCfgStore = useSrParquetCfgStore();
6968
const analysisMapStore = useAnalysisMapStore();
7069
const globalChartStore = useGlobalChartStore();
71-
//const autoReqParamsStore = useAutoReqParamsStore();
7270
const fncs = useFieldNameStore();
7371
const atlChartFilterStore = useAtlChartFilterStore();
7472
const activeTabStore = useActiveTabStore();
73+
const fieldNameStore = useFieldNameStore();
7574
const controls = ref([]);
7675
const tooltipRef = ref<InstanceType<typeof SrCustomTooltip> | null>(null);
7776
// true whenever the active tab is _not_ “3‑D View”
@@ -152,6 +151,7 @@
152151
console.log(msg);
153152
if(newReqId !== oldReqId){
154153
if(newReqId > 0){
154+
await fieldNameStore.loadMetaForReqId(newReqId); // async but don't await
155155
globalChartStore.setAllColumnMinMaxValues({}); // reset all min/max values
156156
await updateAnalysisMapView('watch selectedReqId');
157157
} else {
@@ -175,6 +175,7 @@
175175
176176
onMounted(async () => {
177177
console.log("SrAnalysisMap onMounted using selectedReqId:",props.selectedReqId);
178+
await fieldNameStore.loadMetaForReqId(props.selectedReqId); // async but don't await
178179
// Bind the tooltipRef to the store
179180
if (tooltipRef.value) {
180181
analysisMapStore.tooltipRef = tooltipRef.value;

web-client/src/components/SrAtl03Classification.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ const reqParamsStore = useReqParamsStore();
1919
tooltipText="A set of photon classification values that are designed to identify signal photons for different surface types with specified confidence"
2020
tooltipUrl="https://slideruleearth.io/web/rtd/user_guide/icesat2.html#native-atl03-photon-classification"
2121
v-model="reqParamsStore.enableAtl03Classification"
22-
:defaultValue="reqParamsStore.enableAtl03Classification"
2322
/>
2423
</div>
2524
<div class="sr-atl03-cnf-body">

web-client/src/components/SrCheckbox.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ const props = withDefaults(
6767
tooltipText: '',
6868
tooltipUrl: '',
6969
labelFontSize: 'small',
70-
defaultValue: false,
70+
defaultValue: undefined,
7171
labelOnLeft: false,
7272
}
7373
);
@@ -96,7 +96,10 @@ watch(localChecked, (newValue) => {
9696
});
9797
9898
onMounted(() => {
99-
setValue(props.modelValue, props.defaultValue);
99+
// Only override modelValue with defaultValue if defaultValue was explicitly provided
100+
if (props.defaultValue !== undefined) {
101+
setValue(props.modelValue, props.defaultValue);
102+
}
100103
});
101104
102105
const emitChange = () => {

web-client/src/components/SrExportDialog.vue

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@
66

77
<div v-if="!exporting" class="dialog-body">
88
<div class="dropdown-wrapper">
9-
<label for="formatSelect">Select Format</label>
9+
<label for="currentFormat">Current Format</label>
10+
<div class="current-format-value">
11+
{{ currentFormat }}
12+
</div>
13+
</div>
14+
15+
<div class="dropdown-wrapper">
16+
<label for="formatSelect">Select Exported Format</label>
1017
<Select
1118
id="formatSelect"
1219
v-model="selectedFormat"
@@ -53,6 +60,7 @@ import { createDuckDbClient } from '@/utils/SrDuckDb';
5360
import { useSrParquetCfgStore } from '@/stores/srParquetCfgStore';
5461
import {getArrowFetchUrlAndOptions} from "@/utils/fetchUtils";
5562
import { exportCsvStreamed, getWritableFileStream } from "@/utils/SrParquetUtils";
63+
import { useFieldNameStore } from '@/stores/fieldNameStore';
5664
5765
5866
const props = defineProps<{
@@ -65,17 +73,34 @@ const emit = defineEmits<{
6573
}>();
6674
6775
const toast = useToast();
76+
const fieldNameStore = useFieldNameStore();
6877
const visible = ref(props.modelValue);
6978
const exporting = ref(false);
7079
const selectedFormat = ref<'csv' | 'parquet' | 'geoparquet' | null>(null);
7180
const headerCols = ref<string[]>([]);
7281
const rowCount = ref<number | null>(null);
7382
74-
const formats = [
75-
{ label: 'CSV', value: 'csv' },
76-
{ label: 'Parquet', value: 'parquet' },
77-
{ label: 'GeoParquet', value: 'geoparquet' },
78-
];
83+
const currentFormat = computed(() => {
84+
const isGeo = fieldNameStore.isGeoParquet(props.reqId);
85+
console.log(`[ExportDialog] currentFormat computed - reqId: ${props.reqId}, isGeo: ${isGeo}, asGeoCache:`, fieldNameStore.asGeoCache);
86+
return isGeo ? 'GeoParquet' : 'Parquet';
87+
});
88+
89+
const formats = computed(() => {
90+
const isGeo = fieldNameStore.isGeoParquet(props.reqId);
91+
if (isGeo) {
92+
return [
93+
{ label: 'GeoParquet', value: 'geoparquet' },
94+
{ label: 'CSV', value: 'csv' },
95+
];
96+
} else {
97+
return [
98+
{ label: 'CSV', value: 'csv' },
99+
{ label: 'Parquet', value: 'parquet' },
100+
{ label: 'GeoParquet', value: 'geoparquet' },
101+
];
102+
}
103+
});
79104
80105
const estimatedSizeMB = computed(() => {
81106
if (rowCount.value !== null && headerCols.value.length > 0) {
@@ -103,6 +128,12 @@ onMounted(() => {
103128
watch(visible, async (val) => {
104129
if (val && props.reqId > 0) {
105130
try {
131+
//console.log(`[ExportDialog] Dialog opened for reqId: ${props.reqId}`);
132+
133+
// Load field name metadata including as_geo flag
134+
await fieldNameStore.loadMetaForReqId(props.reqId);
135+
//console.log(`[ExportDialog] After loadMetaForReqId, asGeoCache:`, fieldNameStore.asGeoCache);
136+
106137
const fileName = await db.getFilename(props.reqId);
107138
if (!fileName) return;
108139
const duck = await createDuckDbClient();
@@ -217,7 +248,7 @@ async function exportGeoParquet(fileName: string) : Promise<boolean> {
217248
const metadata = await duckDbClient.getAllParquetMetadata(fileName);
218249
219250
const hasGeoMetadata = metadata && 'geo' in metadata;
220-
251+
//console.log('exportGeoParquet - hasGeoMetadata:', hasGeoMetadata, metadata);
221252
if (hasGeoMetadata) {
222253
// File already has geo metadata, export as-is using exportParquet pattern
223254
console.log('File already has geo metadata, exporting as-is');
@@ -326,6 +357,13 @@ async function exportGeoParquet(fileName: string) : Promise<boolean> {
326357
color: var(--label-color, #374151);
327358
}
328359
360+
.current-format-value {
361+
font-size: 0.875rem;
362+
font-weight: 600;
363+
color: var(--primary-color, #3b82f6);
364+
padding: 0.25rem 0;
365+
}
366+
329367
.size-info {
330368
font-size: 0.875rem;
331369
color: var(--info-color, #6b7280);
@@ -366,6 +404,10 @@ async function exportGeoParquet(fileName: string) : Promise<boolean> {
366404
color: #cbd5e1;
367405
}
368406
407+
.current-format-value {
408+
color: #60a5fa;
409+
}
410+
369411
.size-info {
370412
color: #94a3b8;
371413
}

web-client/src/components/SrGenUserOptions.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,19 @@ onMounted(async () => {
7676

7777
<template>
7878
<div class="sr-gen-user-options-container">
79+
<SrCheckbox
80+
label="as GeoParquet"
81+
v-model="reqParamsStore.isGeoParquet"
82+
tooltipText="When you check this the server will return the data in GeoParquet format"
83+
tooltipUrl="https://geoparquet.org/"
84+
/>
7985
<SrCheckbox
8086
label="Specify Resources (no CMR polygon search)"
8187
v-model="reqParamsStore.ignorePolygon"
8288
tooltipText="When you check this the server skips the CMR polygon search and uses the resources you specify below"
8389
tooltipUrl="https://slideruleearth.io/web/rtd/api_reference/earthdata.html#cmr"
8490
/>
91+
8592
<SrResources v-if="reqParamsStore.ignorePolygon"/>
8693
<Fieldset class="sr-timeouts-fieldset" legend="Timeouts" :toggleable="true" :collapsed="false">
8794
<SrSwitchedSliderInput

web-client/src/components/SrImportParquetFile.vue

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import ProgressBar from 'primevue/progressbar';
55
import Button from 'primevue/button';
66
import SrToast from 'primevue/toast';
77
import { useToast } from 'primevue/usetoast';
8-
import { duckDbLoadOpfsParquetFile, readOrCacheSummary } from '@/utils/SrDuckDbUtils';
8+
import { duckDbLoadOpfsParquetFile, readOrCacheSummary, getGeoMetadataFromFile } from '@/utils/SrDuckDbUtils';
99
import { useRequestsStore } from '@/stores/requestsStore';
1010
import { useRecTreeStore } from '@/stores/recTreeStore';
1111
import { db as indexedDb } from '@/db/SlideRuleDb';
@@ -166,11 +166,6 @@ const customUploader = async (event: any) => {
166166
await directoryHandle.removeEntry(opfsFile.name);
167167
return;
168168
}
169-
if ('geo' in metadata) {
170-
toast.add({ severity: 'error', summary: 'Unsupported File Format', detail: `SlideRule "geo" parquet not supported.`, life: 5000 });
171-
await directoryHandle.removeEntry(opfsFile.name);
172-
return;
173-
}
174169
175170
upload_progress.value = 90;
176171
@@ -205,10 +200,12 @@ const customUploader = async (event: any) => {
205200
206201
upload_progress.value = 95;
207202
208-
// If any of the following throws, well delete the OPFS file to avoid orphans
203+
// If any of the following throws, we'll delete the OPFS file to avoid orphans
209204
try {
210205
const svr_parms_str = await duckDbLoadOpfsParquetFile(newFilename);
211206
srReqRec.svr_parms = svr_parms_str;
207+
const geoMetadata = await getGeoMetadataFromFile(newFilename);
208+
srReqRec.geo_metadata = geoMetadata;
212209
213210
await indexedDb.updateRequestRecord(srReqRec, true);
214211
await recTreeStore.updateRecMenu('From customUploader', srReqRec.req_id);

web-client/src/components/SrLocationFinder.vue

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,21 @@ function updatePosition(lat: number, lon: number) {
4343
highlightEl.style.top = `${globalY}px`;
4444
4545
highlightEl.classList.add('active');
46-
setTimeout(() => highlightEl?.classList.remove('active'), 1000);
46+
setTimeout(() => {
47+
if (highlightEl) {
48+
highlightEl.classList.remove('active');
49+
}
50+
}, 1000);
4751
4852
//console.log('Highlight marker fixed position (body):', { globalX, globalY });
4953
}
5054
5155
function hideMarker() {
52-
if (highlightEl) {
53-
highlightEl.style.left = '-9999px';
54-
highlightEl.style.top = '-9999px';
55-
}
56+
if (!highlightEl) return;
57+
58+
highlightEl.style.left = '-9999px';
59+
highlightEl.style.top = '-9999px';
60+
highlightEl.classList.remove('active');
5661
}
5762
5863
const handleScroll = () => {

web-client/src/components/SrPlotConfig.vue

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,18 +94,26 @@ async function enableLocationFinder() {
9494
if(selectedElRec){
9595
// initialize to selected point on map then update later from plot tooltip formatter
9696
globalChartStore.locationFinderLat = selectedElRec[latField];
97-
globalChartStore.locationFinderLon = selectedElRec[lonField];
97+
globalChartStore.locationFinderLon = selectedElRec[lonField];
9898
}
9999
const reqIdStr = props.reqId.toString();
100100
const currentYData = chartStore.getYDataOptions(reqIdStr);
101101
102+
// Get the actual columns available in the file (elevationDataOptions contains the actual file columns)
103+
const availableColumns = chartStore.getElevationDataOptions(reqIdStr);
104+
105+
// Only add lat/lon if they exist as actual columns in the file (regular parquet)
106+
// For geoparquet, they're extracted from geometry column and don't need to be in yDataOptions
102107
const newFields = [latField, lonField].filter(
103-
field => !currentYData.includes(field)
108+
field => !currentYData.includes(field) && availableColumns.includes(field)
104109
);
105110
106111
if (newFields.length > 0) {
112+
console.log(`enableLocationFinder: Adding ${newFields.join(', ')} to yDataOptions (they exist as separate columns in file)`);
107113
chartStore.setYDataOptions(reqIdStr, [...currentYData, ...newFields]);
108114
await refreshScatterPlot('enabled Link to Elevation Plot');
115+
} else if ([latField, lonField].some(field => !availableColumns.includes(field))) {
116+
console.log(`enableLocationFinder: Skipping lat/lon - they don't exist as separate columns (likely geoparquet with geometry column)`);
109117
}
110118
111119
if (await requestsStore.needAdvice()) {
@@ -134,7 +142,7 @@ onMounted(() => {
134142
135143
watch(() => globalChartStore.enableLocationFinder, async (newVal, oldValue) => {
136144
if (!oldValue && newVal) {
137-
console.log('SrPlotConfig watch enableLocationFinder:', newVal);
145+
//console.log('SrPlotConfig watch enableLocationFinder:', newVal);
138146
enableLocationFinder();
139147
}
140148
});

0 commit comments

Comments
 (0)