Skip to content

Commit f1e4a63

Browse files
committed
Make it easier to get to the "upload polygon" option #618 WIP refactor
1 parent 92352d9 commit f1e4a63

File tree

9 files changed

+645
-417
lines changed

9 files changed

+645
-417
lines changed

web-client/package-lock.json

Lines changed: 589 additions & 243 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web-client/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@
4747
"primeflex": "^3.3.1",
4848
"primeicons": "^7.0.0",
4949
"primevue": "^4.3.2",
50-
"shapefile": "^0.6.6",
5150
"sharp": "^0.34.3",
51+
"shpjs": "^6.1.0",
5252
"vue": "^3.5.6",
5353
"vue-echarts": "^7.0.3",
5454
"vue-router": "^4.4.5",
@@ -65,6 +65,7 @@
6565
"@types/node": "^22.5.5",
6666
"@types/proj4": "^2.5.5",
6767
"@types/shapefile": "^0.6.4",
68+
"@types/shpjs": "^3.4.7",
6869
"@types/uuid": "^10.0.0",
6970
"@vitejs/plugin-vue": "^5.1.3",
7071
"@vue/eslint-config-prettier": "^9.0.0",

web-client/src/components/SrGeoJsonFileUpload.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import Button from 'primevue/button';
66
import SrToast from 'primevue/toast';
77
import { useGeoJsonUploader } from '@/composables/useGeoJsonUploader';
88
import { drawGeoJson, zoomOutToFullMap } from '@/utils/SrMapUtils';
9-
import { load } from 'ol/Image';
109
1110
const props = defineProps({
1211
reportUploadProgress: {
@@ -76,6 +75,7 @@ const onClear = () => {
7675
:maxFileSize="10000000000"
7776
customUpload
7877
:chooseLabel="label"
78+
:chooseIcon="'pi pi-upload'"
7979
@uploader="handleReqUpload"
8080
@select="onSelect"
8181
@error="onError"
@@ -89,11 +89,13 @@ const onClear = () => {
8989
:maxFileSize="10000000000"
9090
customUpload
9191
:chooseLabel="label"
92+
:chooseIcon="'pi pi-upload'"
9293
@uploader="handleFeaturesUpload"
9394
@select="onSelect"
9495
@error="onError"
9596
@clear="onClear"
96-
/> </div>
97+
/>
98+
</div>
9799
</div>
98100
</template>
99101

web-client/src/components/SrMap.vue

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
import SrCustomTooltip from "@/components//SrCustomTooltip.vue";
5252
import SrDropPinControl from "@/components//SrDropPinControl.vue";
5353
import Point from 'ol/geom/Point';
54-
import { readShapefileToOlFeatures,ShapefileInputs } from "@/composables/useReadShapeFile";
54+
import { readShapefileToOlFeatures } from "@/composables/useReadShapefile";
5555
5656
const defaultBathymetryFeatures: Ref<Feature<Geometry>[] | null> = ref(null);
5757
const showBathymetryFeatures = computed(() => {
@@ -561,22 +561,18 @@
561561
}
562562
563563
async function loadDefaultBathymetryFeatures() {
564-
const shpUrl = '/shapefiles/ATL24_Mask_v5.shp';
565-
const dbfUrl = '/shapefiles/ATL24_Mask_v5.dbf';
566-
const shxUrl = '/shapefiles/ATL24_Mask_v5.shx';
567-
568-
const bathyInputs: ShapefileInputs = {
569-
shp: shpUrl,
570-
dbf: dbfUrl,
571-
shx: shxUrl
564+
const bathyFiles = {
565+
shp: '/shapefiles/ATL24_Mask_v5.shp',
566+
dbf: '/shapefiles/ATL24_Mask_v5.dbf',
567+
shx: '/shapefiles/ATL24_Mask_v5.shx'
572568
};
573-
defaultBathymetryFeatures.value = await readShapefileToOlFeatures(bathyInputs);
574-
if(defaultBathymetryFeatures.value && defaultBathymetryFeatures.value.length > 0){
569+
570+
defaultBathymetryFeatures.value = await readShapefileToOlFeatures(bathyFiles);
571+
if (defaultBathymetryFeatures.value?.length) {
575572
loadBathymetryFeatures(defaultBathymetryFeatures.value);
576573
} else {
577-
console.warn("SrMap onMounted no features to load from bathy assets shapefile");
574+
console.warn("No bathymetry features loaded from static shapefile");
578575
}
579-
//console.log("loadDefaultBathymetryFeatures defaultBathymetryFeaturesLoaded:",defaultBathymetryFeaturesLoaded.value);
580576
}
581577
582578
onMounted(async () => {

web-client/src/components/SrShapefileUpload.vue

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,35 +43,22 @@ import type { Feature as OLFeature } from "ol";
4343
import type { Geometry } from "ol/geom";
4444
import { readShapefileToOlFeatures } from "@/composables/useReadShapefile";
4545
46-
// Emit the features array to the parent
4746
const emit = defineEmits<{
4847
(e: "features", features: OLFeature<Geometry>[]): void;
4948
}>();
5049
5150
const showDialog = ref(false);
5251
53-
5452
async function onFilesSelected(event: Event) {
5553
const files = (event.target as HTMLInputElement).files;
5654
if (!files) return;
5755
58-
const shp = Array.from(files).find(f => f.name.endsWith('.shp'));
59-
const dbf = Array.from(files).find(f => f.name.endsWith('.dbf'));
60-
const shx = Array.from(files).find(f => f.name.endsWith('.shx'));
61-
62-
if (!shp || !dbf) {
63-
alert('Please provide at least .shp and .dbf files');
64-
return;
65-
}
66-
6756
try {
68-
const olFeatures = await readShapefileToOlFeatures({ shp, dbf, shx });
57+
const olFeatures = await readShapefileToOlFeatures(files);
6958
emit("features", olFeatures);
7059
showDialog.value = false;
7160
} catch (err) {
72-
// You can use a toast, modal, etc.
7361
alert((err instanceof Error ? err.message : String(err)));
7462
}
7563
}
76-
7764
</script>
Lines changed: 30 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,41 @@
1+
// useReadShapefile.ts
12
import { GeoJSON } from 'ol/format';
23
import type { Feature as OLFeature } from 'ol';
34
import type { Geometry } from 'ol/geom';
4-
import * as shapefile from 'shapefile';
5+
import shp from 'shpjs';
56

6-
// Supports both File and URL string
7-
export type ShapefileInputSource = File | string;
7+
export type FileListInput = FileList;
8+
export type UrlInput = Record<string, string>; // { shp: url, dbf: url, shx?: url }
89

9-
export interface ShapefileInputs {
10-
shp: ShapefileInputSource;
11-
dbf: ShapefileInputSource;
12-
shx?: ShapefileInputSource;
13-
}
14-
15-
/**
16-
* Reads shapefile components and returns OpenLayers features.
17-
* Accepts File or asset URL string for each part.
18-
*/
19-
export async function readShapefileToOlFeatures({
20-
shp,
21-
dbf,
22-
shx,
23-
}: ShapefileInputs): Promise<OLFeature<Geometry>[]> {
24-
try {
25-
const [shpBuf, dbfBuf, shxBuf] = await Promise.all([
26-
loadAsArrayBuffer(shp),
27-
loadAsArrayBuffer(dbf),
28-
shx ? loadAsArrayBuffer(shx) : Promise.resolve(undefined),
29-
]);
30-
31-
const source = await shapefile.open(shpBuf, dbfBuf, shxBuf as any);
32-
const features: any[] = [];
33-
let result: { done: boolean; value: any };
34-
do {
35-
result = await source.read();
36-
if (!result.done) features.push(result.value);
37-
} while (!result.done);
38-
39-
const geojson = {
40-
type: 'FeatureCollection',
41-
features
42-
};
43-
44-
const geojsonFormat = new GeoJSON();
45-
const olFeatures = geojsonFormat.readFeatures(geojson, {
46-
featureProjection: 'EPSG:3857',
47-
}) as OLFeature<Geometry>[];
10+
export async function readShapefileToOlFeatures(
11+
input: FileListInput | UrlInput
12+
): Promise<OLFeature<Geometry>[]> {
13+
let fileMap: Record<string, ArrayBuffer> = {};
4814

49-
return olFeatures;
50-
} catch (err) {
51-
throw new Error("Failed to read shapefile: " + (err instanceof Error ? err.message : String(err)));
15+
if (input instanceof FileList) {
16+
for (const file of Array.from(input)) {
17+
const ext = file.name.split('.').pop()?.toLowerCase();
18+
if (ext) fileMap[ext] = await file.arrayBuffer();
19+
}
20+
} else {
21+
const fetches = await Promise.all(
22+
Object.entries(input).map(([ext, url]) =>
23+
fetch(url).then(resp => {
24+
if (!resp.ok) throw new Error(`Failed to fetch ${url}`);
25+
return resp.arrayBuffer();
26+
})
27+
)
28+
);
29+
fileMap = Object.fromEntries(Object.keys(input).map((k, i) => [k, fetches[i]]));
5230
}
53-
}
5431

55-
/**
56-
* Helper to load File or asset URL string as ArrayBuffer
57-
*/
58-
async function loadAsArrayBuffer(input: ShapefileInputSource): Promise<ArrayBuffer> {
59-
if (input instanceof File) {
60-
return readFileAsync(input);
61-
} else if (typeof input === "string") {
62-
const resp = await fetch(input);
63-
if (!resp.ok) throw new Error(`Failed to fetch asset: ${input}`);
64-
return await resp.arrayBuffer();
65-
} else {
66-
throw new Error('Invalid input type (expected File or URL string)');
32+
if (!fileMap.shp || !fileMap.dbf) {
33+
throw new Error("Missing required .shp or .dbf file");
6734
}
68-
}
6935

70-
// Helper for reading File as ArrayBuffer
71-
async function readFileAsync(file: File): Promise<ArrayBuffer> {
72-
return new Promise((resolve, reject) => {
73-
const reader = new FileReader();
74-
reader.onload = () => resolve(reader.result as ArrayBuffer);
75-
reader.onerror = () => reject(reader.error || new Error('Failed to read file'));
76-
reader.readAsArrayBuffer(file);
77-
});
36+
const geojson = await shp(fileMap);
37+
const geojsonFormat = new GeoJSON();
38+
return geojsonFormat.readFeatures(geojson, {
39+
featureProjection: 'EPSG:3857',
40+
}) as OLFeature<Geometry>[];
7841
}

web-client/src/composables/useShapefileUpload.ts

Lines changed: 0 additions & 74 deletions
This file was deleted.

web-client/src/stores/reqParamsStore.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -578,9 +578,9 @@ const createReqParamsStore = (id: string) =>
578578
req.yapc = yapc;
579579
//console.log('using req.yapc:',req.yapc)
580580
}
581-
if (this.convexHull?.length) {
582-
req.cmr = { polygon: this.convexHull };
583-
}
581+
// if (this.convexHull?.length) {
582+
// req.cmr = { polygon: this.convexHull };
583+
// }
584584

585585
if(this.distanceIn.value === 'segments') {
586586
req.dist_in_seg = true;

web-client/src/types/shpjs.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
declare module 'shpjs' {
2+
export default function shp(
3+
input: Record<string, ArrayBuffer> | ArrayBuffer | string
4+
): Promise<GeoJSON.FeatureCollection>;
5+
6+
// Optional: Add other types if needed
7+
}

0 commit comments

Comments
 (0)